您如何找出异常导致CDI事务回滚的原因?

rac*_*mir 5 java transactions java-ee cdi

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

@Transactional(value=TxType.REQUIRES_NEW)
Run Code Online (Sandbox Code Playgroud)

这将创建一个新的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.
Run Code Online (Sandbox Code Playgroud)

无论如何让CDI重新抛出嵌套错误,以便您可以轻松调试回滚的真正原因是什么?

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

rac*_*mir 8

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

@InterceptorBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionDebugger {
}
Run Code Online (Sandbox Code Playgroud)

一旦我们定义了CDI拦截器,我们就需要创建在使用Interceptor注释时执行的类.我们定义一个@AroundInvoke,以便在我们注释的方法中的代码之前调用我们的代码. invocationContext.proceed()将调用我们注释的方法并向我们返回它返回的结果(如果有的话).所以我们可以try, catch (Exception)绕过这个调用来捕获任何抛出的异常.然后我们可以使用logger(此处使用的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们必须在beans.xml中包含新的Interceptor(通常位于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>
Run Code Online (Sandbox Code Playgroud)

最后,我们必须使用新的CDI Interceptor注释我们调用的方法.这里我们使用@Transactional来注释它们以启动事务,并使用@TransactionDebugger来捕获事务中发生的任何异常:

@Transactional @TransactionDebugger
public void init() {
    ...
}
Run Code Online (Sandbox Code Playgroud)

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