Spring Data Custom Implementation: Entity Not Managed

java, spring, spring-data

Solution

The problem is that you need a transaction when you invoke `refresh()`. All repository methods in Spring Data JPA do this auto-magically for you under the covers. The easiest approach is to use Spring's `@Transactional`, assuming you've enabled Spring transaction management. Eg:

@Transactional
public void execute(JobExecutionContext context) throws JobExecutionException {
  super.logExecution(context);
  super.initialize(context);

  this.extractObjects();

  MyJob myJob = this.myJobRepository.findOne(super.applicationJob.getJobId());
  this.myJobRepository.refresh(myJob);
  this.myJobRepository.save(myJob);
}

The above assumes that the object instance that defines this method is Spring-managed - ie, a Spring bean. The boundary of the transaction extends over the entire method. The down-side with the above approach is that if you invoke `refresh()` in multiple locations you'll need to ensure that it's invoked inside a transaction in each location.

Problem

I have created a web application that uses Spring Data + Hibernate + JPA + Quartz. In one of my Quartz jobs I need to refresh an entity, because the entity may have been changed by a user through the user interface. Basically the entity contains the configuration for the Quartz job and if the entity is changed outside the job the data within the entity is stale, so I want to refresh before I persist the entity so that it does not overwrite the updated information. Since Spring Data does provide a refresh method on repositories, I created a custom implementation for all Repositories in Spring Data. Here is how I setup the custom implementation: CustomRepository.java (Interface) ``` @NoRepositoryBean public interface CustomRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { public void refresh(T entity); } ``` CustomRepositoryImpl.java (Implementation) ``` @NoRepositoryBean public class CustomRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements CustomRepository<T, ID> { private EntityManager entityManager; public CustomRepositoryImpl(Class<T> domainClass, EntityManager entityManager){ super(domainClass, entityManager); this.entityManager = entityManager; } public void refresh(T entity) { this.entityManager.refresh(entity); } } ``` CustomRepositoryFactoryBean.java (Factory Bean for Spring) ``` public class CustomRepositoryFactoryBean<T extends JpaRepository<S,ID>,S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> { protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager){ return new CustomRepositoryFactory(entityManager); } private static class CustomRepositoryFactory<T, ID extends Serializable> extends JpaRepositoryFactory{ private EntityManager entityManager; public CustomRepositoryFactory(EntityManager entityManager) { super(entityManager); this.entityManager = entityManager; } protected Object getTargetRepository(RepositoryMetadata metadata){ return new CustomRepositoryImpl<T, ID>((Class<T>) metadata.getDomainType(), entityManager); } protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata){ return CustomRepository.class; } } } ``` spring-config.xml ``` <jpa:repositories base-package="com.network.socially.data.repositories" factory-class="com.network.socially.data.repositories.custom.CustomRepositoryFactoryBean"> </jpa:repositories> ``` The setup of the custom implementation seems to work when the application initializes via Tomcat, however when I attempt to call the refresh method I receive an exception. Here is the code that calls the refresh method: ``` public void execute(JobExecutionContext context) throws JobExecutionException { super.logExecution(context); super.initialize(context); this.extractObjects(); MyJob myJob = this.myJobRepository.findOne(super.applicationJob.getJobId()); this.myJobRepository.refresh(myJob); this.myJobRepository.save(myJob); } ``` This throws the exception: ``` Caused by: java.lang.IllegalArgumentException: Entity not managed at org.hibernate.ejb.AbstractEntityManagerImpl.refresh(AbstractEntityManagerImpl.java:914) at org.hibernate.ejb.AbstractEntityManagerImpl.refresh(AbstractEntityManagerImpl.java:895) 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:240) at $Proxy25.refresh(Unknown Source) at com.network.socially.data.repositories.custom.CustomRepositoryImpl.refresh(CustomRepositoryImpl.java:22) 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:333) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:318) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155) ... 10 more ``` I don't understand why the entity is not managed since retrieving an entity from the database causes it to be managed in the persistence context. Can anyone point out where I am going wrong? Do I need to use JTA instead of RESOURCE-LOCAL? Why is the entity not managed when I retrieve it via JPA?

Original source