Spring transactional method not working with split thread

I have a Spring application that has a background polling service running on a separate thread to update the state of the data in the database ( EmployeeStatusPollService

). I am using JPA (Hibernate vendor) for a repository. I have implemented this service in two solutions, but only one solution works. Below are two solutions.

Solution 1: A transactional method checkAndUpdateStatus

in another service class and polling 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: The transactional method checkAndUpdateStatus

is in the polling 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 {
    // ....
  }
}

      

Method Details 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 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 doesn't work and I get the "Persistent entity is being detached ..." error when it updates / saves the object in the database on line 3 of the method checkAndUpdateStatus

.

IMO solution 2 doesn't work because the method checkAndUpdateStatus

doesn't fit in the Transactional Context even though it's flagged @Transactional

. Even when I installed REQUIRES_NEW

it still doesn't work. Can anyone explain to me about this error why this happened, or send me some help documentation about this?

+3


source to share


1 answer


Transactional annotation works by creating a proxy in the original class, and local or internal calls to methods within the class do not result in a proxied method being called. In other words, a call from a method in a class to another method in the same class is not intercepted by the proxy.

To explain this specifically in your case:

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

The proxied version of the checkAndUpdateStatus method adds the required transactional behavior and then calls the original checkAndUpdateStatus method. However, this causes any call to the checkAndUpdateStatus method from your class instance to be called directly on that instance and will not be intercepted by the proxy.



Therefore, in the second example, when "checkAndUpdateStatus" is called internally from another EmployeeStatusPollService method, it is not called on the proxy version of EmployeeStatusPollService, which has transactional capabilities, but on a regular one.

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

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

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

+6


source







All Articles