【问题标题】:Spring 4: @async nativeQuery running before the calling @transactional transaction commitsSpring 4:@async nativeQuery 在调用@transactional 事务提交之前运行
【发布时间】:2017-04-03 20:12:55
【问题描述】:

我在 Spring web @Controller 中有一个 @transactional 方法,它调用了一个 @async 方法。 @transactional 方法将实体持久化到 Spring-wired javax.persistence.entitymanager 并调用 @async 函数将一些衍生数据保存到分析服务器。 @async 函数依赖于来自 NativeQuery 的结果,该结果来自它自己的 @PersistenceContext

问题是@async 函数内部的本机查询在第一个事务刷新之前运行,这意味着实体尚未刷新到数据库。

确保@async 函数仅在完成当前打开的事务之后运行的最佳方法是什么?我尝试将@async 函数包装在它自己的@transactional(propagation = Propagation.REQUIRES_NEW) 注释中,并在调用异步方法之前在第一个事务中手动调用em.flush(),但都没有改变结果。

这是我的代码的简化版本(编辑-添加了一些额外的细节):

网络控制器

@Controller
class WebController {
    @PersistenceContext
    EntityManager em;

    @Autowired
    AsyncService service;

    @RequestMapping("/createObject")
    @Transactional
    @ResponseBody
    public Obj createObj(Obj obj){
        em.persist(obj);

        //Run the time consuming async task for the newly-persisted entity
        service.runAsyncMethod(obj.getId());

        return obj;
    }

}

异步服务

@Service
@EnableAsync
class AsyncService {

     @PersistenceContext
     EntityManager em;

     @Async
     public void runMethod(Long itemId) {
         //Run native query
         Query query = em.createNativeQuery("select name from obj where id = :objId")
         query.setParameter("objId", itemId);


         List results = query.getResultsList()
         //`results` is empty
     }
}

另外:如果我在持久性配置中打开hibernate.show_sql,则打印的 SQL 语句看起来就像它们的顺序正确:

    Hibernate: insert into content (id, name) values (?, ?)
    Hibernate: select name from obj where id = ?

最后,如果我将 service.runAsyncMethod() 调用封装在 java.util.TimerTask 中并安排它延迟 1 秒,它确实按预期工作。

在我的应用程序中从多个不同的 Web 端点调用 asyncMethod。

有没有办法(可能通过 AOP?)告诉异步函数只在当前事务刷新后运行?

【问题讨论】:

  • 如果不提交数据,另一个事务无法看到数据。如果您希望它在另一个方法之后运行,那么在另一个方法之后运行它。创建一个(非事务性)服务,该服务首先调用该方法来持久化内容,然后调用另一个方法。
  • 你是对的——这可能是我提供的简化示例的最佳解决方案。不幸的是,异步方法是从许多不同的事务性@RequestMapping 方法中调用的,这需要一个很大的重构来包装它们。传递给异步方法的数据取决于调用的方法,因此将其包装在 servlet 过滤器中会很棘手。
  • 呃? Servlet 过滤器?那是从哪里来的?另外,为什么异步调用必须更改...您想结合此处的内容更改调用它的位置。显然,由于过于简化,您提出的问题不会给您想要/需要的答案。

标签: java spring hibernate transactions


【解决方案1】:

如果在事务提交(刷新完成)之后调用异步方法,我们可以实现这一点。这可以通过调用TransactionSynchronizationAdapter(Spring提供的抽象类)的重写的afterCommit()中的async方法并在TransactionSynchronizationManager中注册这个TransactionSynchronizationAdapter对象来实现(有助于事务同步)。

例如:

@Controller
class WebController {
   @PersistenceContext
   EntityManager em;

   @Autowired
   AsyncService service;

   @Transactional
   public Obj createObj(Obj obj){
        em.persist(obj);

        // Call the Async method only after transaction commit.
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {

           // Override the afterCommit which need to be executed after transaction commit
           public void afterCommit() { 
              //Run the time consuming async task for the newly-persisted entity
              service.runAsyncMethod(obj.getId());
           }
         }

        return obj;
      }

    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-23
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    • 2023-03-20
    • 2023-03-10
    相关资源
    最近更新 更多