持久化脱管对象报未标记级联PERSIST错误 求J2EE与Spring差异原因
错误根本原因
你遇到的during synchronization a new object was found through a relationship that was not marked cascade PERSIST: Wallet ...报错,核心原因是外层createUser事务的持久化上下文(Persistence Context,下称PC)将关联到User的Wallet识别为未持久化的瞬时对象,而非已经入库的托管对象。
对应的业务代码逻辑如下:
// 现有事务上下文 public void createUser(){ User user = userRepo.createUser(); walletService.createAndAttachWalletToUser(user); // 新增以下代码即可修复 // user.setWallet(walletService.merge(user.getWallet())); 或者 // user.setWallet(walletService.getWallet(user.getWallet().getId())); } // WalletService类 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void createAndAttachWalletToUser(User user){ Wallet wallet = walletRepo.createWallet(); user.setWallet(wallet); }
J2EE与Spring事务/持久化上下文的核心差异
1. 持久化上下文的绑定规则不同
- J2EE(EJB容器)默认使用事务作用域的持久化上下文:
每个独立事务对应完全独立的PC实例,事务提交/回滚后PC立即销毁。你在createAndAttachWalletToUser的REQUIRES_NEW独立事务中创建的Wallet,仅属于该新事务的PC,新事务提交后PC关闭,Wallet变为游离态。回到外层createUser事务时,外层PC没有该Wallet的托管记录,直接将游离态Wallet关联到外层PC托管的User上,外层PC flush校验时就会判定这是未持久化的新对象,抛出对应错误。 - Spring默认使用线程绑定的扩展持久化上下文:
Spring的JpaTransactionManager默认会将PC绑定到当前请求线程,同线程内的所有事务(哪怕是REQUIRES_NEW开启的独立事务)都会复用同一个PC实例。你在新事务中创建的Wallet会直接加入线程共享的PC,回到外层事务时Wallet仍处于托管态,因此不会触发校验错误。
2. 跨事务实体的托管状态处理逻辑不同
- J2EE EJB的事务隔离设计更严格,跨事务传递的实体默认直接转为游离态,不会自动合并到新事务的PC中,必须显式调用
merge或重新查询才能让实体重新进入托管态。 - Spring的PC传播逻辑会自动维持同线程内实体的托管状态,跨事务传递的实体不会主动转为游离态,无需手动合并就能被外层事务的PC识别。
修复方案原理验证
你找到的两种修复方案本质都是让外层事务的PC识别到Wallet的已持久化状态:
- 调用
merge方法会将游离态的Wallet实例的状态同步到外层PC,生成对应的托管实例关联到User - 重新查询Wallet会直接从数据库读取数据,返回外层PC托管的实例,替换掉之前的游离态实例
内容的提问来源于stack exchange,提问作者waynewing
相关产品推荐
相关产品推荐

