JPA事务疑问:标记为TransactionAttributeType.REQUIRED的事务方法未结束,实体却已提前写入数据库
你遇到的情况其实是EJB事务管理中一个很容易踩的坑——同一个Bean内部的方法调用不会触发容器的事务拦截器,导致createEntities上的@TransactionAttribute(REQUIRED)根本没有生效!
具体分析
你的aMethod标注了NOT_SUPPORTED,这意味着该方法执行时不会开启或使用任何事务。当你在aMethod内部直接调用createEntities时,这是Bean内部的方法调用,不会经过EJB容器的代理层,容器也就不会去检查createEntities上的事务属性。结果就是createEntities是在无事务的环境下执行的。
而JPA的EntityManager在无事务环境下的默认行为是自动提交:每次执行merge(或者persist)操作后,它会立即将变更flush到数据库并提交。这就是为什么你在System.out.println处暂停时,外部工具已经能看到数据库里的新实体——因为每次merge都触发了自动提交,根本没有事务在管理这些操作。
验证你的预期
如果createEntities的事务属性能正常生效(也就是在事务环境下执行),那么你的预期是完全正确的:所有merge的变更会被暂存在EntityManager的持久化上下文中,直到事务提交时才会一次性flush到数据库,外部工具在事务结束前是看不到这些未提交的变更的(除非数据库隔离级别设为READ_UNCOMMITTED,但这不是常规配置)。
解决方案
要让createEntities的事务属性生效,你需要让调用经过EJB容器的代理,有两种常见方式:
1. 将createEntities移到独立的EJB中
把创建实体的逻辑抽离到一个新的Stateless Bean里,然后在MyClass中注入这个Bean并调用:
@Stateless public class EntityCreator { @Inject private MyService myService; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void createEntities() { myService.createEntity(new MyEntity(1, "111")); System.out.println("Paused execution, DB should NOT see this yet"); myService.createEntity(new MyEntity(2, "222")); System.out.println("Paused execution, DB should NOT see this yet"); } } @Stateless public class MyClass { @Inject private EntityCreator entityCreator; @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void aMethod() { entityCreator.createEntities(); // 外部调用,触发事务拦截器 } }
2. 通过SessionContext获取当前Bean的代理
如果你不想拆分Bean,可以注入SessionContext,通过它获取当前Bean的代理实例,再调用createEntities:
@Stateless public class MyClass { @Inject private MyService myService; @Resource private SessionContext sessionContext; @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void aMethod() { // 获取当前Bean的代理 MyClass proxy = sessionContext.getBusinessObject(MyClass.class); proxy.createEntities(); // 通过代理调用,触发事务拦截器 } @TransactionAttribute(TransactionAttributeType.REQUIRED) public void createEntities() { myService.createEntity(new MyEntity(1, "111")); System.out.println("Paused execution, DB won't see this until transaction ends"); myService.createEntity(new MyEntity(2, "222")); System.out.println("Paused execution, DB won't see this until transaction ends"); } }
额外补充:关于merge的使用
另外,你这里用merge来创建新实体其实不太规范——对于全新的、没有持久化过的实体,应该用em.persist(entity)而不是merge。merge主要用于更新游离实体(比如从前端传入的、脱离了持久化上下文的实体)。虽然merge对于新实体也能工作,但persist更符合语义,性能也更好。
内容的提问来源于stack exchange,提问作者Paco Abato

