You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA事务疑问:标记为TransactionAttributeType.REQUIRED的事务方法未结束,实体却已提前写入数据库

问题根源:EJB内部方法调用不触发事务拦截器

你遇到的情况其实是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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 08:47:38