共计 1534 个字符,预计需要花费 4 分钟才能阅读完成。
我这边有个批量插入用户 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 上下文失效范畴,就能够解决该异样了