为何TransactionManager/UserTransaction操作不入库,@Transactional却可正常生效?
问题解答
1. 手动事务管理失效的根本原因
你写的手动事务代码无法生效,核心问题是没有处理JPA实体的状态:
- 你传入
deleteEntity方法的baseEntity是游离态实体:该实体是上游Controller层查询出来的,和上游的持久化上下文已经脱离关联,你直接修改游离态实体的deletedBy字段,JPA的持久化上下文感知不到这个变更,所以第一个事务提交后不会把修改同步到数据库。 - 第二个事务直接调用游离态实体的
delete()方法,Panache的删除操作要求实体处于托管状态,游离态实体执行删除会直接触发事务回滚,所以抛出你遇到的RollbackException异常。
至于测试代码能正常运行,是因为你在第二个事务里先执行了Offer.findById(newOffer.id),这个操作会把实体重新加载到当前事务的持久化上下文,变回托管状态,再执行删除自然不会报错,和你业务代码的逻辑本质不一样。
2. TransactionManager和UserTransaction的区别
二者都是JTA规范定义的事务控制接口,适用场景不同:
TransactionManager是给容器、框架使用的底层事务接口,除了基础的事务开启/提交/回滚能力外,还暴露了事务挂起、恢复、线程上下文绑定等底层能力,普通业务开发几乎不需要直接使用。UserTransaction是给应用开发者使用的高层封装接口,只暴露了业务开发需要的基础事务操作,屏蔽了底层上下文绑定的复杂逻辑,如果你需要手动控制事务,优先选择UserTransaction即可。
你两个接口都报错,本质和接口本身无关,核心原因还是前面说的实体状态不正确,所以不管用哪个接口都会触发异常。
3. 现有代码简化方案
你目前的声明式事务实现逻辑是正确的,只需要把通用逻辑内聚到DeletionService中即可解决冗余问题:
@ApplicationScoped public class DeletionService { @Inject CurrentUser currentUser; @Transactional(Transactional.TxType.REQUIRES_NEW) private BaseEntity markDeleted(BaseEntity entity) { BaseEntity managed = entity.isPersistent() ? entity : entity.getEntityManager().merge(entity); managed.deletedBy = currentUser.userId; managed.persist(); return managed; } @Transactional(Transactional.TxType.REQUIRES_NEW) private void doDelete(BaseEntity entity) { BaseEntity managed = entity.isPersistent() ? entity : entity.getEntityManager().merge(entity); managed.delete(); } // 对外暴露统一调用入口 public void deleteWithPreMark(BaseEntity entity) { BaseEntity updated = markDeleted(entity); doDelete(updated); } }
改造后OfferService的代码可以简化为:
@ApplicationScoped public class OfferService { @Inject DeletionService deletionService; public void deleteOffer(Offer offer) { deletionService.deleteWithPreMark(offer); } }
后续其他实体需要同类操作时,直接调用deleteWithPreMark方法即可,无需重复编写事务逻辑。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

