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

EJB Hibernate嵌套事务未回滚求助:已标注@ApplicationException(rollback=true)

问题:嵌套事务异常后未按预期回滚

编写测试代码验证嵌套事务更新两个实体的逻辑,启动嵌套事务尝试更新两个实体,但transaction3()方法抛出异常后,相关实体的更新未按预期回滚,相关配置与代码如下:

persistence.xml

<persistence version="2.0"
   xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
        http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="sac">      
      <jta-data-source>java:jboss/datasources/sacDS</jta-data-source>
      <class>org.hibernate.envers.DefaultRevisionEntity</class>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
         <property name="hibernate.show_sql" value="false"/>
         <property name="hibernate.format_sql" value="true"/>         
         <property name="hibernate.hbm2ddl.auto" value="validate"/>         
      </properties>     
   </persistence-unit>
</persistence>

EnvioException

@javax.ejb.ApplicationException(rollback=true)
public class EnvioException extends javax.ws.rs.WebApplicationException {
    private static final long serialVersionUID = -990622645054458959L;
}

REST API

@Path("/transactionService")
public class TransactionService {
    @EJB
    TestTransaction testTransaction;

    @Path("/transaction")
    @GET
    @SemAutenticacao
    public void transaction(@Context HttpHeaders headers) throws IOException, SQLException {
        testTransaction.transaction1();
    }
}

核心业务类 TestTransaction

@Stateless
public class TestTransaction {
    @Inject
    protected EntityManager em;

    public void transaction1() throws IOException, SQLException {
        transaction2();
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void transaction2() throws IOException, SQLException {
        TransactionEntity transactionEntity = em.find(TransactionEntity.class, 1L);
        transactionEntity.setNome("Joe");
        
        try {
            transaction3();
        } catch(EnvioException e) {
            System.out.println("Exception Handled here!");
        }
    }
        
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void transaction3() throws EnvioException {
        Transaction2Entity transaction2Entity = em.find(Transaction2Entity.class, 2L);
        transaction2Entity.setNome("Joe");
        
        throw new EnvioException();
    }
}
解决方案

问题根源

  1. 同一Bean内方法调用不触发事务代理:无状态EJB内部的方法调用不会经过容器的事务拦截器,因此transaction3()上的@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)完全不生效,它会和transaction2()共用同一个事务。
  2. 异常捕获导致事务未回滚:EnvioException被标记为@ApplicationException(rollback=true),但你在transaction2()中捕获了该异常,容器不会自动触发当前事务的回滚,导致所有实体更新都被提交。

修复步骤

1. 拆分EJB,确保事务属性生效

将transaction3()方法移到独立的无状态EJB中,这样跨Bean调用时会经过容器代理,REQUIRES_NEW才会创建新事务:

@Stateless
public class Transaction3Service {
    @Inject
    protected EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void transaction3() throws EnvioException {
        Transaction2Entity transaction2Entity = em.find(Transaction2Entity.class, 2L);
        transaction2Entity.setNome("Joe");
        throw new EnvioException();
    }
}

在原TestTransaction中注入这个新EJB并调用:

@Stateless
public class TestTransaction {
    @Inject
    protected EntityManager em;
    @EJB
    private Transaction3Service transaction3Service;

    public void transaction1() throws IOException, SQLException {
        transaction2();
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void transaction2() throws IOException, SQLException {
        TransactionEntity transactionEntity = em.find(TransactionEntity.class, 1L);
        transactionEntity.setNome("Joe");
        
        try {
            transaction3Service.transaction3();
        } catch(EnvioException e) {
            System.out.println("Exception Handled here!");
            // 若需要回滚transaction2的事务,手动标记
            TransactionSynchronizationRegistry registry = InitialContext.doLookup("java:comp/TransactionSynchronizationRegistry");
            registry.setRollbackOnly();
        }
    }
}

2. 手动控制事务回滚

  • transaction3()所在的新事务会因异常自动回滚,transaction2Entity的更新会被撤销。
  • 如果需要transaction2()的事务也回滚,在捕获异常后通过TransactionSynchronizationRegistry手动标记事务为仅回滚状态,容器会在方法结束时回滚该事务。

3. 验证事务边界

确保所有需要独立事务的方法都通过跨Bean调用触发,避免同一Bean内的直接调用,这是EJB事务属性生效的前提。

内容的提问来源于stack exchange,提问作者Renato

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 03:33:20