当前位置: 首页 > 知识库问答 >
问题:

Spring事务性方法不能在分离的线程上工作

柳豪
2023-03-14

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

解决方案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 {
    // ....
  }
}
@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时,它仍然不起作用。有没有人能解释一下这个错误,为什么会发生,或者给我一些参考文档?

共有1个答案

花稳
2023-03-14

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

在您的案例中具体解释一下:

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

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

因此,在第二个示例中,当从EmployeeStatusPollService的另一个方法内部调用“CheckandUpdateStatus”时,它不是在具有事务性功能的EmployeeStatusPollService的代理版本上调用,而是在普通版本上调用。

第一个示例之所以起作用,是因为checkAndUpdateStatus在另一个类中,因此外部调用被代理拦截。

您可以在这里阅读关于事务性与代理创建一起工作的更多信息:https://spring.io/blog/2012/05/23/transactions-caching-and-aop-derlanding-proxy-usage in-spring

您可以在这里了解代理过程的工作原理:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-commanding-aop-proxies

 类似资料:
  • 我是一个新的Spring和学习的事务概念。无法使@Transactional工作。 用例: 当getEmployee()抛出RuntimeException时,员工和员工详细信息的数据插入应该回滚。但回滚没有发生。我使用的是Oracle数据库11g和spring 4.3.1版本。下面是正在运行的独立java代码。

  • 我有一个事务性方法,在它的内部,一个实体被实例化,并使用persist方法插入到Hibernate上下文中。然后更改实体的某些属性(因此它将反映在数据库中)。如果对实体调用detach方法,然后更改实体的某些属性,会发生什么情况。当方法完成(和事务提交)时,Hibernate是否会插入实体并将属性更新到分离调用之前的点? 例如:

  • 我创建了一个MySQL数据库,并填充了用于测试的行。我想在这个数据库上进行DAO单元测试。每个都是,因此每次测试后都会进行回滚。不幸的是,它无法工作,因为我的数据库仍在进行更改。 我正在用以下上下文加载Spring配置。xml 这个问题说我 必须在应用程序上下文中提供bean 但即使有了它(在我的上下文中是),什么都没有发生,我的数据库仍然被修改,没有回滚。 这是我的DAO测试课 在我的配置中,或

  • 我有一个带有异步endpoint的quarkus应用程序,它创建一个具有默认属性的实体,在request方法中启动一个新线程,并执行一个长期运行的作业,然后返回该实体作为响应供客户端跟踪。 此外,长时间运行的作业将在实体运行时对其进行更新,因此它也必须是事务性的。但是,数据库实体没有得到更新。 这些是我面临的问题: 收到以下警告: 我尝试使用但没有用。 我尝试在上使用API方法,而不是在指南中提到

  • 问题内容: 我正在尝试Java线程方法的示例。但是我发现即使线程已经启动,该方法仍在返回。有人可以告诉我我在做什么错吗?这是代码片段。 问题答案: 如果我的记忆很好,那么java在线程切换之间会有很长的时间间隔,因此isAlive可能会失败,因为线程 尚未 激活。尝试在thread.start()和thread.isAlive()之间添加一些等待时间

  • 嗨,我正在尝试开发带有事务的spring和hibernate应用程序,我正在使用Spring4。x和hibernate 4。下面是我的代码片段 应用程序上下文。xml servlet上下文。xml 坚持。xml finnaly meservice看起来像这样 和 这里会发生运行时异常,但db记录没有回滚。