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

时间:2013-09-19 07:25:10

标签: java java-ee transactions 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 个答案:

答案 0 :(得分:8)

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

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

一旦我们定义了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;
    }
}

接下来,我们必须在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 Interceptor注释我们调用的方法。这里我们使用@Transactional来注释它们以启动事务,并使用@TransactionDebugger来捕获事务中发生的任何异常:

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

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

答案 1 :(得分:0)

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

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

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