Spring Transactional method not work on separated Thread

java, spring, spring-transactions

Solution

Transactional annotation works by creating a proxy on the original class, and local or internal calls to methods within a class do not cause the proxied method to be invoked. In other words, a call from a method in a class to another method in the same class does not get intercepted by the proxy.

To explain that specifically in your case:

For transactional annotation to work, spring creates a proxy around EmployeeStatusPollService class that will wrap your EmployeeStatusPollService class instance to intercept calls to checkAndUpdateStatus method that has the transactional annotation.

The proxied version of the checkAndUpdateStatus method adds the desired transactional behaviour and afterwards calls the original checkAndUpdateStatus method. This has the effect however, that any call to the checkAndUpdateStatus method from within your class instance is directly invoked on that instance and will not be intercepted by the wrapping proxy.

Therefore, in the second example, when the "checkAndUpdateStatus" is called internally from another method of the EmployeeStatusPollService, it is not called on the proxied version of the EmployeeStatusPollService that has transactional capability, but on the normal one.

The first example worked because the checkAndUpdateStatus was in a different class and therefore the external call was intercepted by the proxy.

You can read more about the fact that transactional works with the creation of proxies here: https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring

And you can read here about how the proxying procedure works: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies

Problem

I have a Spring-based application that has a background polling service runs in a separated thread to update status of data in database (`EmployeeStatusPollService`). I use JPA (Hibernate vendor) for the Repository. I implemented this service in two solutions, but only one solution works. Below are the two solutions. Solution 1: Transactional method `checkAndUpdateStatus` in other service class and poll service calls it ``` @Service public class EmployeeStatusPollService implements Runnable { @Inject private EmployeeService employeeService; private static final int DEFAULT_PAGE_SIZE = 300; private boolean flag = true; public EmployeeStatusPollService() { } @PostConstruct public void start() { SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); executor.setConcurrencyLimit(SimpleAsyncTaskExecutor.NO_CONCURRENCY); executor.execute(this); } public void run() { while(flag) { try { int pagenum = 1; List<Employee> items = null; do { items = employeeService.checkAndUpdateStatus(pagenum, DEFAULT_PAGE_SIZE); } while(items != null && items.size() == DEFAULT_PAGE_SIZE); } catch(Exception ex) { } } } } @Service public class EmployeeServiceImpl implements EmployeeService { private static final Logger LOG = LoggerFactory.getLogger(EmployeeServiceImpl.class); @Inject private EmployeeRepository employeeRepository; @Transactional public List<Employee> checkAndUpdateStatus(int pagenum, int pagesize) throws Exception { // .... } } ``` Solution 2: Transactional method `checkAndUpdateStatus` is in poll service class ``` @Service public class EmployeeStatusPollService implements Runnable { @Inject private EmployeeRepository employeeRepository; private static final int DEFAULT_PAGE_SIZE = 300; private boolean flag = true; public EmployeeStatusPollService() { } @PostConstruct public void start() { SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); executor.setConcurrencyLimit(SimpleAsyncTaskExecutor.NO_CONCURRENCY); executor.execute(this); } public void run() { while(flag) { try { int pagenum = 1; List<Employee> items = null; do { items = checkAndUpdateStatus(pagenum, DEFAULT_PAGE_SIZE); } while(items != null && items.size() == DEFAULT_PAGE_SIZE); } catch(Exception ex) { } } } @Transactional public List<Employee> checkAndUpdateStatus(int pagenum, int pagesize) throws Exception { // .... } } ``` Details of method `checkAndUpdateStatus` ``` @Transactional public List<Employee> checkAndUpdateStatus(int pagenum, int pagesize) throws Exception { PageRequest page = new PageRequest(pagenum, pagesize); Page<Employee> pagedItems = employeeRepository.findByStatus(EmployeeStatus.PENDING, page); // Line 1: Query employees List<Employee> emps = pagedItems.getContent(); List<Long> updatedItems = new ArrayList<>(); int i = 0; for(Employee emp:emps) { try { // ... emp.setStatus(status); // Line 2: Update employee's status employeeRepository.save(emp); // Line 3: Save/Update employee updatedItems.add(emp.getId()); i++; if(i % 50 == 0) { employeeRepository.flush(); // Line 4: flush for every 50 employees } //.... } catch (Exception ex) { // handle exception here.... } } return emps; } ``` Configuration ``` <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="defaultAutoCommit" value="false" /> </bean> <bean id="entityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="jpaDialect" ref="jpaDialect"></property> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"></property> <property name="packagesToScan" value="${jpa.packagesToScan}" /> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> <prop key="hibernate.connection.autocommit">${hibernate.connection.autocommit}</prop> <prop key="hibernate.connection.defaultAutoCommit">${hibernate.connection.defaultAutoCommit}</prop> </props> </property> </bean> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> ``` Solution 2 does not work and I get the error "Persistent entity is detached..." when it updates/saves the entity to the database at the Line 3 of method `checkAndUpdateStatus`. IMO, Solution 2 does not work because the method `checkAndUpdateStatus` isn't being put in Transactional Context although it's marked by `@Transactional`. Even when I set `REQUIRES_NEW` it still doesn't work. Could anyone can explain me about this error why it happened or send me some reference documentation about this?

Original source