提问者:小点点

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


我们使用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 上运行)


共2个答案

匿名用户

执行此操作的最简单方法似乎是使用 CDI 拦截器来捕获异常。我们可以按如下方式定义 CDI 拦截器:

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

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

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

@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;
    }
}

接下来,我们必须将新的拦截器包含在我们的bean.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() 代码时发生的任何错误。通过将 try,catch 从异常更改为侦听器实现类事务接收器中的异常子类,可以更改日志记录粒度。

匿名用户

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

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

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