当前位置: 首页 > 后端技术 > Java

spring jpa关于线程池异步执行导致detached entity passed to persist问题排查和解决

时间:2023-04-01 20:20:16 Java

排查解决spring中线程池异步执行导致的detachedentitypassedtopersist问题jpa操作是异步执行的,但是遇到了一个jpadetachedentitypassedtopersist的问题。我这里的操作是批量保存一个OpenAppUser关联表,所以需要先获取OpenUser和OpenApp对应的引用,然后设置到关联对象OpenAppUser,然后保存,我先通过userRepository.findById(userId),然后openAppUser.setOpenUser(openUser),当执行appUserRepository.save(openAppUser);出现了如题的错误,说是OpenUser对象处于浮动状态,无法保存。经查,是因为OpenAppUser类中设置了@ManyToOne(cascade=CascadeType.ALL)级联OpenUser,所以保存OpenAppUser时,OpenUser会被级联。在不异步开启线程的情况下,因为OpenUser是之前被findById查出来的,所以在jpa的PersistenceContext中有一个OpenUser的可分离对象,此时不会报错,在里面没有可分离对象线程异步时的上下文(这里解释一下,为什么不开启线程,或者开启线程?)因为spring-boot默认jpa.open-in-view=true,所以会使用ThreadLocal保存EntityManager上下文信息在当前线程,所以在整个controller中使用相同的contextPersistenceContext持久化上下文,有两种类型:transaction-scoped持久化上下文;当我们在事务中执行任何操作时,EntityManager会检查持久化上下文。如果存在,它将被使用。否则,它会创建一个扩展持久性上下文范围的持久性上下文;扩展的持久性上下文可以跨越多个事务。我们可以在没有事务的情况下持久化实体,但不能在没有事务的情况下刷新它。在@PersistenceContext注解中,类型可以指定范围:PersistenceContextType.TRANSACTION;PersistenceContextType.EXTENDED当我们异步使用线程池时,是获取不到之前EntityManager的配置信息的,而springjparepository默认的方法会有一个transaction,所以执行userRepository.findById(userId)获取OpenUser后,会提交,提交操作会清空EntityManager中保存的detached对象OpenUser,等待appUserRepository.save(openAppUser);保存的时候,由于引用的OpenUser已经不在PersistenceContext上下文中,它不是一个detached对象(参见EntityStateentityState=getEntityState(entity,entityName,entityEntry,source));具体判断有几个标准是不是detached对象,是否有id,version等),都会报detachedentity传给persist的异常。所以,我们根据实际情况,只需要参考open-in-view=true,生成对应的OpenEntityManagerInViewInterceptor拦截器,修改自己线程中PersistenceContext上下文的有效范围即可。异常可以解决