Пружинный Транзакционный способ не работает на разделенной нити


У меня есть приложение на основе Spring, в котором фоновая служба опроса выполняется в отдельном потоке для обновления состояния данных в базе данных (EmployeeStatusPollService). Я использую JPA (Hibernate vendor) для репозитория. Я реализовал эту услугу в двух решениях, но работает только одно решение. Ниже приведены два решения.

Решение 1: Транзакционный метод checkAndUpdateStatus в другом классе обслуживания и служба опроса вызывает его

@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 {
    // ....
  }
}

Решение 2: Транзакционный метод checkAndUpdateStatus находится в опросе класс обслуживания

@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 {
    // ....
  }
}

Детали метода 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;
}

Конфигурация

<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>

Решение 2 не работает, и я получаю ошибку " постоянная сущность отсоединена..."когда он обновляет/сохраняет сущность в базе данных в строке 3 метода checkAndUpdateStatus.

ИМО, решение 2 не работает, потому что метод checkAndUpdateStatus не помещается в транзакционный контекст, хотя он помечен @Transactional. Даже когда я устанавливаю REQUIRES_NEW, это все равно не работает. Мог кто-нибудь может объяснить мне об этой ошибке, почему это произошло, или прислать мне какую-нибудь справочную документацию по этому поводу?
1 3

1 ответ:

Транзакционная аннотация работает путем создания прокси-сервера в исходном классе, и локальные или внутренние вызовы методов внутри класса не приводят к вызову проксированного метода. Другими словами, вызов метода в классе другому методу в том же классе не перехватывается прокси-сервером.

Чтобы объяснить это конкретно в вашем случае:

Для работы транзакционной аннотации spring создает прокси-сервер вокруг класса EmployeeStatusPollService, который будет обертывать ваш Экземпляр класса EmployeeStatusPollService для перехвата вызовов метода checkAndUpdateStatus, имеющего транзакционную аннотацию.

Проксированные версия метода checkAndUpdateStatus добавляет нужные операционное поведение, а затем вызывает оригинальную методику checkAndUpdateStatus. Однако это приводит к тому, что любой вызов метода checkAndUpdateStatus из экземпляра класса вызывается непосредственно на этом экземпляре и не будет перехвачен прокси-сервером обертывания.

Поэтому во втором примере, когда" checkAndUpdateStatus " вызывается внутренне из другого метода EmployeeStatusPollService, он вызывается не на проксированной версии EmployeeStatusPollService, которая имеет транзакционные возможности, а на обычной.

Первый пример работал, потому что checkAndUpdateStatus был в другом классе, и поэтому внешний вызов был перехвачен прокси-сервером.

Вы можете прочитать больше о том, что транзакционные работы с созданием прокси здесь: https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring

И вы можете прочитать здесь о том, как работает процедура проксирования: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies