Spring Boot + Atomikos: исключение, генерируемое в транзакционном слушателе JMS внутри try-catch, немедленно вызывает откат - PullRequest
0 голосов
/ 25 апреля 2018

Я использую Spring Boot 1.5.3 и Atomikos 4.0.6. У меня есть DefaultMessageListenerContainer настройка для прослушивания очереди JMS и обработки входящих сообщений в транзакции с использованием Spring JTATransactionManager, настроенного для использования Atomikos. Обработчик сообщений вызывает транзакционную службу, которая пытается обработать сообщение внутри try-catch-block, а в случае исключений вызывает другие транзакционные методы для целей ведения журнала. Идея состоит в том, что транзакция должна быть откатана только после того, как все будет зарегистрировано, и возникшее исключение RuntimeException выбрасывается из catch-block.

@Transactional
public void handleMessage(UnmarshalledMessage message) {
    try {
        Thing thing = repository.find(message.getId());
        ...
    }
    catch (Exception e) {
        // NoResultException translated into EmptyResultDataAccessException
        logger.logUsingTransactions(e.getMessage());
        throw e;
    }
}

Однако, что происходит, когда транзакция откатывается сразу же после того, как она изначально была выброшена в repository.find (). При попытке чтения из базы данных внутри блока catch возникает исключение, поскольку транзакция была помечена как только откатная:

c.atomikos.jdbc.AtomikosSQLException - Transaction is marked for rollback only or has timed out
com.atomikos.datasource.xa.session.InvalidSessionHandleStateException: Transaction is marked for rollback only or has timed out
    at com.atomikos.datasource.xa.session.NotInBranchStateHandler.checkEnlistBeforeUse(NotInBranchStateHandler.java:39)
    at com.atomikos.datasource.xa.session.TransactionContext.checkEnlistBeforeUse(TransactionContext.java:70)
    at com.atomikos.datasource.xa.session.SessionHandleState.notifyBeforeUse(SessionHandleState.java:160)
    at com.atomikos.jdbc.AtomikosConnectionProxy.enlist(AtomikosConnectionProxy.java:207)
    at com.atomikos.jdbc.AtomikosConnectionProxy.invoke(AtomikosConnectionProxy.java:122)
    at com.sun.proxy.$Proxy129.prepareStatement(Unknown Source)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.prepareStatement(DatabaseAccessor.java:1565)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.prepareStatement(DatabaseAccessor.java:1514)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseCall.prepareStatement(DatabaseCall.java:778)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:621)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:560)
    at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2055)
    at org.eclipse.persistence.sessions.server.ServerSession.executeCall(ServerSession.java:570)
    at org.eclipse.persistence.sessions.server.ClientSession.executeCall(ClientSession.java:258)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:242)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:228)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.selectOneRow(DatasourceCallQueryMechanism.java:714)
    at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectOneRowFromTable(ExpressionQueryMechanism.java:2803)
    at org.eclipse.persistence.internal.queries.ExpressionQueryMechanism.selectOneRow(ExpressionQueryMechanism.java:2756)
    at org.eclipse.persistence.queries.ReadObjectQuery.executeObjectLevelReadQuery(ReadObjectQuery.java:555)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1175)
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1134)
    at org.eclipse.persistence.queries.ReadObjectQuery.execute(ReadObjectQuery.java:441)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1222)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1790)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.executeQuery(EntityManagerImpl.java:911)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.findInternal(EntityManagerImpl.java:854)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.find(EntityManagerImpl.java:730)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.find(EntityManagerImpl.java:599)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)

Я хотел бы знать, что является причиной такого поведения и как решить проблему. Обратите внимание, что эта настройка работала правильно при запуске в Weblogic. Для получения дополнительной информации вот журнал трассировки транзакций, когда исключение впервые встречается внутри метода репозитория.

DEBUG o.s.t.jta.JtaTransactionManager - Initiating transaction commit
DEBUG o.s.t.jta.JtaTransactionManager - Creating new transaction with name [myMessageListenerContainer]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG o.s.t.jta.JtaTransactionManager - Participating in existing transaction
TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [my.transactional.messagehandler.handleMessage]
DEBUG o.s.t.jta.JtaTransactionManager - Participating in existing transaction
TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [my.repository.class.method]
TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [my.repository.class.method] after exception: org.springframework.dao.EmptyResultDataAccessException: ProcessableMessage with id 443e73e7-0905-416b-9e03-4aaa2bbf09fb; nested exception is javax.persistence.NoResultException: ProcessableMessage with id [message-id]
TRACE o.s.t.i.RuleBasedTransactionAttribute - Applying rules to determine whether transaction should rollback on org.springframework.dao.EmptyResultDataAccessException: ProcessableMessage with id 443e73e7-0905-416b-9e03-4aaa2bbf09fb; nested exception is javax.persistence.NoResultException: ProcessableMessage with id [message-id]
TRACE o.s.t.i.RuleBasedTransactionAttribute - Winning rollback rule is: null
TRACE o.s.t.i.RuleBasedTransactionAttribute - No relevant rollback rule found: applying default rules
DEBUG o.s.t.jta.JtaTransactionManager - Participating transaction failed - marking existing transaction as rollback-only
DEBUG o.s.t.jta.JtaTransactionManager - Setting JTA transaction rollback-only

EDIT:

Я использую JPA, а NoResultException изначально создается следующим образом:

public static <T> T mandatoryFind(EntityManager em, Class<T> type, Object id) throws NoResultException {
        T found = em.find(type, id);
        if (found == null) {
            throw new NoResultException(type.getSimpleName() +" with id "+ id);
        }
        return found;
}

Который, в свою очередь, вызывается из класса, помеченного @Transactional(noRollbackFor = NoResultException.class). Исключение возникает в довольно обычном случае использования - меня беспокоит то, почему транзакция откатывается, прежде чем я могу обработать исключение?

1 Ответ

0 голосов
/ 25 апреля 2018

Похоже, вы используете JPA под капотом (на основе ваших журналов отладки). Это типичное поведение, тогда вы запрашиваете один результат.

NoResultException:

Выдается провайдером персистентности, когда Query.getSingleResult () или TypedQuery.getSingleResult () выполняются в запросе, и результат не возвращается. Это исключение не приведет к тому, что текущая транзакция, если она активна, будет помечена для отката.

Чтобы избежать такого поведения, вы запрашиваете результат как List.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...