1

I was working on defining a One to Many bidirectional relationship between entities A and B and realized that the entityManager should be refreshed after persisting A for B to be updated with A. This is explained here

At the service layer, instead of repeatedly calling

entityManager.refresh(entity)

after jpaRepository.saveAndFlush(entity) , I decided to implement EntityListeners.

I faced some issues while injecting entityManager to the EntityListener and resolved the same using the steps here

To summarize,

@Entity
public class Book {

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private Long id;

   @ManyToOne(cascade={CascadeType.REFRESH})
   private BookCatalog bookCatalog;

   ...
}

@Entity
public class BookCatalog {
   ....
   @OneToMany(mappedBy="bookCatalog")
   private List<Book> books = new ArrayList<>();
   ....
}

@Component
public class LibraryEntityListener {

 @PostPersist
 @PostUpdate
 public void updateBookCatalog(Book book) {
    EntityManager entityManager = BeanUtil.getBean(EntityManager.class);
    entityManager.refresh(book);
 }
}

@Component
public class BookServiceImpl implements BookService {

  @PersistenceContext
  private EntityManager entityManager;

  @Autowired
  private BookCatalogJpaRepository bookCatalogJpaRepository;

  @Autowired
  private BookJpaRepository bookJpaRepository;

  @Override
  public BookCatalog addBookCatalogToLibrary(BookCatalog bookCatalog) {
   BookCatalog catalog = bookCatalogJpaRepository
        .saveAndFlush(bookCatalog);
   notificationService.notifySubscriberForNewAddition(catalog);
   return catalog;
  }

  @Override
  public Book addBookToTheCatalog(Long bookCatalogId, Book book) {
     BookCatalog bookCatalog = new BookCatalog();
     bookCatalog.setId(bookCatalogId);
     book.setBookCatalog(bookCatalog);
     return bookJpaRepository.saveAndFlush(book);
  }
}

but I am getting an exception at entityManager.refresh(book);

org.hibernate.AssertionFailure: null identifier
 at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:51)
 at org.hibernate.internal.AbstractSessionImpl.generateEntityKey(AbstractSessionImpl.java:338)
 at org.hibernate.event.internal.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:131)
 at org.hibernate.event.internal.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:44)
 at org.hibernate.internal.SessionImpl.fireRefresh(SessionImpl.java:1180)
 at org.hibernate.internal.SessionImpl.refresh(SessionImpl.java:1153)
 at org.hibernate.internal.SessionImpl.refresh(SessionImpl.java:1148)
 at org.hibernate.jpa.spi.AbstractEntityManagerImpl.refresh(AbstractEntityManagerImpl.java:1227)
 at org.hibernate.jpa.spi.AbstractEntityManagerImpl.refresh(AbstractEntityManagerImpl.java:1192)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
 at com.sun.proxy.$Proxy105.refresh(Unknown Source)
 at com.foo.library.repository.LibraryEntityListener.updateBookCatalog(LibraryEntityListener.java:19)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.hibernate.jpa.event.internal.jpa.ListenerCallback.performCallback(ListenerCallback.java:35)
 at org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl.callback(CallbackRegistryImpl.java:94)
 at org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl.postCreate(CallbackRegistryImpl.java:63)
 at org.hibernate.jpa.event.internal.core.JpaPostInsertEventListener.onPostInsert(JpaPostInsertEventListener.java:38)
 at org.hibernate.action.internal.EntityIdentityInsertAction.postInsert(EntityIdentityInsertAction.java:156)
 at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:102)
 at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:619)
 at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:273)
 at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:254)
 at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:299)
 at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:317)
 at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:272)
 at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:178)
 at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:109)
 at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
 at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
 at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
 at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
 at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775)
 at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748)
 at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753)
 at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
 at com.sun.proxy.$Proxy105.persist(Unknown Source)
 at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:508)
 at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:522)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:504)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:489)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
 at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
 at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
 at com.sun.proxy.$Proxy112.saveAndFlush(Unknown Source)
 at com.foo.library.service.impl.BookServiceImpl.addBookToTheCatalog(BookServiceImpl.java:50)
 at com.foo.library.service.BookServiceIntegrationTest.testAddBookToTheCatalog(BookServiceIntegrationTest.java:287)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
 at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
 at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
 at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
 at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
 at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
 at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
 at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
 at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
 at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

Questions:

  1. saveAndFlush(entity) : here flush means that the entity is flushed (persisted) in the database right? In that case, at @PostPersist, why the hibernate entityEntry is unable to get the id of the Book entity
  2. How to resolve this issue? I need to, by default, refresh the entity in the persistence context right after it is persisted, without having to call refresh after every save method. Is there any other way of achieving this?
  3. All I am trying here, is to update BookCatalog with the newly created book. Is there anyother way to achieve this? What if I use merge instead of refresh? Would that be right?
  4. If I do find a way to implement this using entityManager.refresh(), what happens during a transaction rollback? I have a scenario for logging a missing book.
    • close the rent
    • mark the book as missing
    • enter penalty for the user
      Each of these steps handled by JpaRepository actions. If entering the penalty failed, does the entityManager.refresh() that happened for rent and book rolls back?
Community
  • 1
  • 1

1 Answers1

0

From the documentation:

A callback method must not invoke EntityManager or Query methods!

Instead, simply add the book to the catalog if you really need to update the inverse side in the same persistence context instance:

bookCatalog.getBooks().add(book)
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110