当我添加@Async 时,entityManager.flush() 将引发异常 [英] When I add @Async, entityManager.flush() will raise an exception

查看:46
本文介绍了当我添加@Async 时,entityManager.flush() 将引发异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的是 Spring 框架.

我的项目结构是Controller ➡️ Service ➡️ Logic.

我在 Logic 类中添加了 @Transactional.我正在使用 EntityManager 进行数据库操作.在每次数据库操作(选择、更新...)之后,我调用 entityManager.flush() 方法.一切都很好.

但是为了提高性能,我在Service类中添加了@Async.然后当我调用 entityManager.flush() 时引发异常.

javax.persistence.TransactionRequiredException:没有正在进行的事务在 org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3505)在 org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1427)在 org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423)在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)在 java.lang.reflect.Method.invoke(Method.java:498)在 org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350)在 com.sun.proxy.$Proxy150.flush(来源不明)在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)在 java.lang.reflect.Method.invoke(Method.java:498)在 org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:305)在 com.sun.proxy.$Proxy150.flush(来源不明)

我调试了一下源码,发现EntityManager是使用ThreadLocal绑定线程的,但是当我添加@Async的时候,会出现一个新线程,这个新线程会创建一个新的EntityManager,就OK了.但是当我调用 entityManager.flush() 时,它会检查 Transaction 并调用 JdbcResourceLocalTransactionCoordinatorImpl.isJoined() 方法,physicalTransactionDelegate 为 null,因此会引发异常.>

physicalTransactionDelegate 在主线程中初始化.

如果要执行entityManager.flush()怎么办?还是我对来源的理解有误?

控制器.java

@GetMapping(value = "/getTest",产生 = MediaType.APPLICATION_JSON_UTF8_VALUE)公共 ResponseDto 执行(@Valid final RequestDto requestDto){列表<CompletableFuture<Dto>>completableFutures = new ArrayList<>();for (ItemRequestDto item : requestDto.getItemRequestDtoList()) {completableFutures.add(service.execute(item));}}

Service.java

@Async("taskExecutor")public CompletableFuture执行(最终 ItemRequestDto 项目){返回 CompletableFuture.completedFuture(logic.execute(item));}

逻辑.java

@Transactional(rollbackFor = Throwable.class, timeout = 60)public ResponseDto execute(final ItemRequestDto item) {//过程...}

我在 github 中创建了一个问题.https://github.com/spring-projects/spring-framework/issues/23325

解决方案

我认为这是一个错误.

  • 没有@Async
    • 当请求到来时,TransactionSynchronizationManager.bindResource((Object key, Object value)会被调用,一个新的EntityManager会绑定到当前线程.
    • 然后,调用EntityManagerFactoryUtils.doGetTransactionalEntityManager(EntityManagerFactory emf, @Nullable Map properties, boolean synchronizedWithTransaction) 获取绑定的EntityManager.在此方法中,它将调用 EntityManager.joinTransaction().这个方法会调用JdbcResourceLocalTransactionCoordinatorImpl.getTransactionDriverControl()来初始化TransactionDriverControlImpl physicalTransactionDelegate.
    • 当我们调用 EntityManager.flush() 时.JdbcResourceLocalTransactionCoordinatorImpl.isJoined()会被调用来检查.
  • 使用@Async
    • 当请求到来时,TransactionSynchronizationManager.bindResource((Object key, Object value)会被调用,一个新的EntityManager会绑定到当前线程.
    • 如果存在@Async,则会创建一个新线程.
    • 然后,调用EntityManagerFactoryUtils.doGetTransactionalEntityManager(EntityManagerFactory emf, @Nullable Map properties, boolean synchronizedWithTransaction) 获取绑定的EntityManager.但是在这个新线程中,没有绑定EntityManager,所以会创建一个新的,但是EntityManager.joinTransaction()不会被调用.所以TransactionDriverControlImpl physicalTransactionDelegate不会被初始化.
    • 当我们调用 EntityManager.flush() 时.由于 TransactionDriverControlImpl physicalTransactionDelegate 为 null,将引发异常.

为什么在新线程中创建新的 EntityManager 时不调用 EntityManager.joinTransaction()?

I'm using Spring framework.

The structure of my project is Controller ➡️ Service ➡️ Logic.

I added @Transactional in Logic class. I am using EntityManager to do DB operations. After every db operation(select, update...), I am calling entityManager.flush() method. Everything is fine.

But in order to improve performance, I added @Async in Service class. Then an exception is raised when I am calling entityManager.flush().

javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3505)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1427)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1423)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350)
    at com.sun.proxy.$Proxy150.flush(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:305)
    at com.sun.proxy.$Proxy150.flush(Unknown Source)

I debugged the source and found that EntityManager is bound to thread by using ThreadLocal, but when I added @Async, there will be a new thread, this new thread will create a new EntityManager, that is OK. But when I call entityManager.flush(), it will check Transaction and call JdbcResourceLocalTransactionCoordinatorImpl.isJoined() method,physicalTransactionDelegate is null, so the exception will be raised.

physicalTransactionDelegate is initialized in main thread.

What should I do if I want to execute entityManager.flush()? Or is my understanding about the source wrong?

Controller.java

@GetMapping(value = "/getTest", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseDto execute(@Valid final RequestDto requestDto) {
        List<CompletableFuture<Dto>> completableFutures = new ArrayList<>();
        for (ItemRequestDto item : requestDto.getItemRequestDtoList()) {
            completableFutures.add(service.execute(item));
        }
    }

Service.java

@Async("taskExecutor")
    public CompletableFuture<InventoryInfoListDto> execute(final ItemRequestDto item) {
        return CompletableFuture.completedFuture(logic.execute(item));
    }

Logic.java

@Transactional(rollbackFor = Throwable.class, timeout = 60)
public ResponseDto execute(final ItemRequestDto item) {
    // process...
}

I created an issue in github. https://github.com/spring-projects/spring-framework/issues/23325

解决方案

I think this is a bug.

  • With No @Async
    • When request comes, TransactionSynchronizationManager.bindResource((Object key, Object value) will be called, and a new EntityManager will be bound to current thread.
    • Then, EntityManagerFactoryUtils.doGetTransactionalEntityManager(EntityManagerFactory emf, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) is called to get the bound EntityManager. Inside this method, it will call EntityManager.joinTransaction(). This method will call JdbcResourceLocalTransactionCoordinatorImpl.getTransactionDriverControl() to initialize TransactionDriverControlImpl physicalTransactionDelegate.
    • When we call EntityManager.flush(). JdbcResourceLocalTransactionCoordinatorImpl.isJoined()will be called to check.
  • With @Async
    • When request comes, TransactionSynchronizationManager.bindResource((Object key, Object value) will be called, and a new EntityManager will be bound to current thread.
    • If there is a @Async, a new thread will be created.
    • Then, EntityManagerFactoryUtils.doGetTransactionalEntityManager(EntityManagerFactory emf, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) is called to get the bound EntityManager. But in this new thread, there is no bound EntityManager, so a new one will be created, but EntityManager.joinTransaction() will not be called. So TransactionDriverControlImpl physicalTransactionDelegate will not be initialized.
    • When we call EntityManager.flush(). Since TransactionDriverControlImpl physicalTransactionDelegate is null, an exception will be raised.

Why EntityManager.joinTransaction() is not called when a new EntityManager is created in a new thread?

这篇关于当我添加@Async 时,entityManager.flush() 将引发异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆