【问题标题】:Spring Transactional method not work on separated ThreadSpring Transactional 方法不适用于单独的线程
【发布时间】:2023-03-31 06:19:01
【问题描述】:

我有一个基于 Spring 的应用程序,它有一个后台轮​​询服务在一个单独的线程中运行,以更新数据库中的数据状态 (EmployeeStatusPollService)。我使用 JPA(Hibernate 供应商)作为存储库。我在两种解决方案中实现了这项服务,但只有一种解决方案有效。以下是两种解决方案。

方案一:其他服务类中的事务方法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 不起作用,当它在方法 checkAndUpdateStatus 的第 3 行将实体更新/保存到数据库时,我收到错误“持久实体已分离...”。

IMO,解决方案 2 不起作用,因为方法 checkAndUpdateStatus 没有被放入事务上下文中,尽管它由 @Transactional 标记。即使我设置了REQUIRES_NEW,它仍然不起作用。任何人都可以向我解释为什么会发生此错误或向我发送一些有关此的参考文档吗?

【问题讨论】:

    标签: java spring spring-transactions


    【解决方案1】:

    事务注释通过在原始类上创建代理来工作,并且对类中方法的本地或内部调用不会导致调用代理方法。换句话说,从一个类中的一个方法对同一类中另一个方法的调用不会被代理拦截。

    具体解释你的情况:

    为了使事务注解起作用,spring 围绕 EmployeeStatusPollService 类创建了一个代理,该代理将包装您的 EmployeeStatusPollService 类实例,以拦截对具有事务注解的 checkAndUpdateStatus 方法的调用。

    checkAndUpdateStatus 方法的代理版本添加了所需的事务行为,然后调用原始的 checkAndUpdateStatus 方法。但是,这会产生这样的效果,即从您的类实例中对 checkAndUpdateStatus 方法的任何调用都会直接在该实例上调用,并且不会被包装代理拦截。

    因此,在第二个示例中,当从 EmployeeStatusPollService 的另一个方法内部调用“checkAndUpdateStatus”时,它不是在具有事务能力的 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

    【讨论】:

    • 总体正确答案,但您提供了一些关于 OP 如何 使其在单个类中工作的指针。
    • @Ordous,我同意你的观点,但这里的问题是,在使用代理时,这在一个类中并不可行,而在使用 spring aop 时,代理是不可避免的。我能想象的唯一选择是使用 AspectJ 并使用编译时编织对其进行配置,这样它就不会为其创建代理。然而,在这种情况下,解决方案#1 就足够了,也是解决问题的最简单方法。如果您有任何想法,请随时提出其他解决方案
    • 最常用的两个是:@Autowire 将服务连接到自身,或者使用显式事务管理器(您可以请求 Spring 并自己强制执行事务传播)。 可能在 SO 中的某处与这个确切的东西有一个问题,但我太困了,无法搜索。无论如何,你已经有了我的 +1,除非 OP 需要进一步的帮助,否则不值得大惊小怪。
    • 我遇到了同样的问题。我在同一个类中调用了另一个 @Transactional 方法。在尝试访问集合时,我收到了 no session 错误。我最初不明白这个问题,所以我使用TransactionTemplate inside run 方法以编程方式创建了事务并解决了。现在我从答案中明白了为什么它最初不起作用。
    • 谢谢你的解释;但即使在使用第一个解决方案时,我明确地抛出了一个 runtimeException,但数据库不回滚,它提交了在异常之前所做的更改
    猜你喜欢
    • 1970-01-01
    • 2019-11-18
    • 1970-01-01
    • 2020-10-25
    • 2011-01-22
    • 1970-01-01
    • 2011-05-22
    • 1970-01-01
    相关资源
    最近更新 更多