我这边有个批量插入用户OpenUser和利用OpenApp关联关系数据的操作,因为耗时较长时间,所以筹备用线程池异步执行操作,然而却遇到了一个jpa的detached entity passed to persist问题,我这边的操作是批量保留一个OpenAppUser关联关系表,所以须要先取得对应OpenUser和OpenApp的援用,再设置到关联对象OpenAppUser里,而后在保留,我这边是先通过userRepository.findById(userId)获取到OpenUser,而后openAppUser.setOpenUser(openUser),在执行appUserRepository.save(openAppUser);时产生了如题目上的谬误,说是OpenUser对象处于游离态,无奈保留。

通过排查,我这边是因为OpenAppUser类里设置了@ManyToOne(cascade = CascadeType.ALL)级联OpenUser,所以在保留OpenAppUser的时候会级联操作OpenUser,原本在没有开线程异步的状况下,因为OpenUser之前通过findById查出来了,所以在jpa的PersistenceContext里是有该OpenUser的脱管对象的,这时候就不会报错,而在线程异步的状况下context里确没有该脱管对象了
(这里阐明一下,为啥不开线程有,开了线程没有?)因为spring-boot默认jpa.open-in-view=true,会应用ThreadLocal在以后线程里保留EntityManager上下文信息,所以在整个controller里都是应用的同一个context

PersistenceContext持久性上下文有两种类型:

  • 事务范畴的持久性上下文;当咱们在事务中执行任何操作时,EntityManager 会查看持久性上下文。 如果存在,则将应用它。否则,它将创立一个持久性上下文
  • 扩大范畴的持久性上下文;扩大持久性上下文能够逾越多个事务。咱们能够在没有事务的状况下长久化实体,但不能在没有事务的状况下刷新它。

在@PersistenceContext注解里type能够指定范畴:PersistenceContextType.TRANSACTION;PersistenceContextType.EXTENDED

而当咱们用线程池异步的时候,拿不到之前的EntityManager的配置信息,而spring jpa repository默认的办法上都会自带一个事务,所以在执行完userRepository.findById(userId)获取到OpenUser之后,会commit,而commit操作会clear掉EntityManager里保留的脱管对象OpenUser,等到appUserRepository.save(openAppUser);保留的时候,因为援用的OpenUser曾经没有在PersistenceContext上下文里了,不是脱管对象了(具体能够看EntityState entityState = getEntityState( entity, entityName, entityEntry, source );外面的实现,有几种判断条件,是不是脱管对象,有没有id、version等等属性),就会报detached entity passed to persist这个异样

所以依据理论状况,咱们只有参考open-in-view=true产生对应的OpenEntityManagerInViewInterceptor拦截器革新一下本人线程里的PersistenceContext上下文失效范畴,就能够解决该异样了