5

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

4

2 回答 2

8

似乎最简单的方法是使用 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的子类来改变。

于 2013-11-22T08:17:23.617 回答
0

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

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

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

于 2013-09-20T12:26:06.463 回答