Как узнать, какое исключение вызвало откат транзакции CDI?

Мы используем CDI с CMT (транзакции, управляемые контейнером) для подключения к базе данных в веб-приложении и помечаем методы, вызываемые из внешнего интерфейса, которые требуют транзакции, с помощью:

@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)


person rachekalmir    schedule 19.09.2013    source источник


Ответы (2)


Кажется, самый простой способ сделать это - использовать перехватчик CDI для перехвата исключений. Мы можем определить CDI Interceptor следующим образом:

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

Как только мы определили CDI Interceptor, нам нужно создать класс, который будет выполняться при использовании аннотации Interceptor. Мы определяем @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 (). Гранулярность ведения журнала можно изменить, изменив try, catch с Exception на подкласс Exception в классе реализации Interceptor TransactionInterceptor.

person rachekalmir    schedule 22.11.2013

TransactionalException выдается во время фиксации, то есть после того, как ваш код был полностью выполнен. Поскольку транзакция помечена для отката, фиксация не может быть выполнена, и создается исключение.

Однако транзакция была помечена для отката где-то во время выполнения. Я предполагаю, что вы не помечаете его для отката вручную, поэтому должно быть создано исключение. Исключение составляет либо RuntimeException, либо помечено @ApplicationException(rollback = true). Поскольку вы не получаете этого исключения, исключение должно быть где-то обнаружено. Вы уверены, что не улавливаете исключения, возникающие из бизнес-метода в вашем коде?

Чтобы ответить на вопрос ... Нет, я не думаю, что можно повторно выбросить исходное исключение, потому что оно выбрасывается в другое время и в другом месте.

person Mareen    schedule 20.09.2013
comment
Бизнес-методы явно не перехватывают никаких исключений. CDI, должно быть, где-то отлавливает эту ошибку, поэтому было бы интересно посмотреть, есть ли способ заставить его (по крайней мере) регистрировать обнаруженную ошибку. - person rachekalmir; 25.09.2013