【问题标题】:How do you find out what Exception caused the CDI Transaction Rollback?您如何找出导致 CDI 事务回滚的异常?
【发布时间】:2013-09-19 07:25:10
【问题描述】:

我们使用 CDI 和 CMT(容器管理事务)连接到 Web 应用程序中的数据库,并标记从前端调用的需要事务的方法:

@Transactional(value=TxType.REQUIRES_NEW)

这将创建一个新的 CDI 事务,但是现在如果执行此代码块或从此方法调用的任何其他代码块发生异常,它将抛出错误消息:

javax.transaction.TransactionalException: Managed bean with Transactional annotation and TxType of REQUIRES_NEW encountered exception during commit javax.transaction.RollbackException: Transaction marked for rollback.
...
Caused by: javax.transaction.TransactionalException: Managed bean with Transactional annotation and TxType of REQUIRES_NEW encountered exception during commit javax.transaction.RollbackException: Transaction marked for rollback.
...
Caused by: javax.transaction.RollbackException: Transaction marked for rollback.

有没有办法让 CDI 重新抛出嵌套错误,以便您可以轻松调试回滚的真正原因是什么?

(在 Java-EE7、Glassfish 4.0、JSF 2.2.2 上运行)

【问题讨论】:

    标签: java jakarta-ee transactions cdi


    【解决方案1】:

    似乎最简单的方法是使用 CDI 拦截器来捕获异常。我们可以这样定义CDI拦截器:

    @InterceptorBinding
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TransactionDebugger {
    }
    

    一旦我们定义了 CDI 拦截器,我们需要创建在使用拦截器注解时执行的类。我们定义了一个@AroundInvoke,以便在我们注释的方法中的代码之前调用我们的代码。 invocationContext.proceed() 将调用我们注解的方法并给我们它返回的结果(如果有的话)。所以我们可以在这个调用周围放置一个try, catch (Exception) 来捕获任何类型的异常抛出。然后我们可以使用记录器(这里使用 log4j)记录这个异常,并重新抛出异常,以便任何上游代码也被告知它。

    重新抛出异常也将允许我们使用 CMT(容器管理事务),因为最终容器将捕获异常并抛出事务 RollbackException。但是,您也可以轻松地使用 UserTransactions 并在捕获到异常时执行手动回滚,而不是重新抛出它。

    @Interceptor
    @TransactionDebugger
    public class TransactionInterceptor {
        private Logger logger = LogManager.getLogger();
    
        @AroundInvoke
        public Object runInTransaction(InvocationContext invocationContext) throws Exception {
            Object result = null;
            try {
                result = invocationContext.proceed();
            } catch (Exception e) {
                logger.error("Error encountered during Transaction.", e);
                throw e;
            }
            return result;
        }
    }
    

    接下来我们必须在 beans.xml 中包含我们的新拦截器(通常位于 src/META-INF),因为在 CDI 中默认情况下不启用拦截器。这必须在使用注释的所有项目中完成,而不仅仅是定义注释的项目。这是因为 CDI 在每个项目的基础上初始化拦截器:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                               http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
           version="1.1" bean-discovery-mode="all">
        <interceptors>
            <class>package.database.TransactionInterceptor</class>
        </interceptors>
    </beans>
    

    最后,我们必须对我们使用新的 CDI 拦截器调用的方法进行注释。这里我们用@Transactional 注释它们来启动一个事务,并用@TransactionDebugger 来捕捉事务中发生的任何异常:

    @Transactional @TransactionDebugger
    public void init() {
        ...
    }
    

    这将记录执行 init() 代码时发生的任何错误。日志粒度可以通过在Interceptor实现类TransactionInterceptor中将try、catch从Exception改成Exception的子类来改变。

    【讨论】:

      【解决方案2】:

      TransactionalException 在提交时抛出,即在您的代码完全执行之后。由于事务被标记为回滚,因此无法进行提交并抛出异常。

      但是,事务在执行期间的某个时间被标记为回滚。我假设您没有手动将其标记为回滚,因此必须抛出异常。例外是RuntimeException 或带有@ApplicationException(rollback = true) 注释。由于您没有收到此异常,因此该异常一定是在某个地方捕获的。您确定没有在代码中捕获业务方法抛出的异常吗?

      回答这个问题...不,我认为不可能重新抛出原始异常,因为它是在不同的时间和地点抛出的。

      【讨论】:

      • 业务方法没有明确捕获异常。 CDI 必须在某个地方捕捉到这个错误,所以看看是否有办法让它(至少)记录它捕捉到的错误会很有趣。
      猜你喜欢
      • 2013-03-27
      • 2020-03-10
      • 2015-10-03
      • 2013-10-16
      • 1970-01-01
      • 2012-12-28
      • 1970-01-01
      • 2013-11-23
      • 1970-01-01
      相关资源
      最近更新 更多