Spring Service @Transactional не отменяет транзакцию Mybatis SqlSession

Цель состоит в том, чтобы откатить все/любые транзакции в случае сбоя. Но это не работает, как ожидалось.

Мы используем Spring MVC + JMS + Service + Mybatis. В логах JMS настроен на откат, но вставляется строка а не откат. Хотел бы узнать, что я упускаю или делаю неправильно?

Недавно был добавлен тег @Transactional. Поэтому не уверен, что он работает так, как ожидалось.

Код:

Класс обслуживания:

@Transactional(value = "transactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class DataExchangeLogic implements DataExchangeService {

private DataExchDao dataExchDao;

...

    @Override
    public void save(DataExch dataExch) throws ValidationException {
        if (dataExch.getId() != null && dataExch.getId() > 0) {
            this.dataExchDao.update(dataExch);
        } else {
            //LOGGER.debug("in insert::");
            this.dataExchDao.create(dataExch);
            //Empty exception throw to test rollback
            throw new RuntimeException();
        }
    }
}

ДАО:

public interface DataExchDaoMybatis
extends NotificationDao {

void create(DataExch dataExch);

}

Весенний контекст

<bean id="dataExchLogic"  class="com.abc.service.logic.DataExchLogic">
        <property name="dataExchDao" ref="dataExchDao" />
</bean>

Проект EAR/WAR Spring Context

<!-- Transaction Manager -->
    <bean id="transactionManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager" />

<tx:annotation-driven transaction-manager="transactionManager" />

Журналы:

[31mWARN [0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Setup of JMS message listener invoker failed for destination 'queue://REQUEST?priority=1&timeToLive=500000' - trying to recover. Cause: Transaction rolled back because it has been marked as rollback-only 
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:720)
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:240)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1142)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1134)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1031)
    at java.lang.Thread.run(Thread.java:745)
[34mINFO [0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Successfully refreshed JMS Connection 
[39mDEBUG[0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Received message of type [class com.ibm.ws.sib.api.jms.impl.JmsTextMessageImpl] from consumer [com.ibm.ws.sib.api.jms.impl.JmsQueueReceiverImpl@6ca01c74] of transactional session [com.ibm.ws.sib.api.jms.impl.JmsQueueSessionImpl@3ac3b63] 
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@206ee277]
JDBC Connection [com.ibm.ws.rsadapter.jdbc.WSJdbcConnection@19b89f0c] will be managed by Spring
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # ==>  Preparing: SELECT ID.NEXTVAL FROM DUAL  
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # ==> Parameters:  
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # <==      Total: 1 
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # ==>  Preparing: INSERT INTO TABLE ( COL1, COL2, COL N) VALUES ( ?, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?)  
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # ==> Parameters: 468(Integer), SYSTEM(String), 2017-03-01 00:00:00.0(Timestamp), 2017-03-16 00:00:00.0(Timestamp), true(Boolean), test 112(String), ALL(String) 
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # <==    Updates: 1 
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@206ee277]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@206ee277]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@206ee277]

ИЗМЕНИТЬ 1:

Код контроллера:

@ResourceMapping(value = "addNewDEURL")
    public void addNewDE(@ModelAttribute(value = "dataObject") final DataExch dataExch,
                                   final BindingResult bindingResult, final ResourceResponse response) {
        if (!bindingResult.hasErrors()) {

            try {
                dataExchangeService.save(dataExch);
            } catch (final ValidationException e) {
                logger.error("A validation exception occurred.", e);
            }                           
        } else {
            logger.error(bindingResult.getAllErrors().get(0)
                .getDefaultMessage());
        }
    }

DAO изменено:

public class DataExchDaoMybatis extends BaseDaoImpl implements DataExchDao {

public void create(DataExch dataExch) {
        doSimpleInsert("insertDE", dataExch);
    }
}

BaseDaoImpl:

public void doSimpleInsert(String queryId, Object o) {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert(queryId, o);
}

person Harry    schedule 07.03.2017    source источник
comment
Поддерживает ли ваша БД транзакцию?   -  person Blank    schedule 08.03.2017
comment
Оракл - это БД. Спасибо   -  person Harry    schedule 08.03.2017
comment
Убедитесь, что ваш @Transactional сервис не будет проксироваться другим фреймворком. Согласно вашей информации, я предполагаю, что DataExchangeLogic был проксирован фреймворком JMS, что приведет к тому, что @Transactional не будет работать. Я предлагаю вам обернуть вашу службу DataExchangeLogic и внедрить правильный DataExchangeLogic в качестве члена и получить доступ через этого члена.   -  person Gemini Keith    schedule 09.03.2017
comment
@GeminiKeith Не могли бы вы помочь с небольшим примером того, как этого добиться? Большое спасибо   -  person Harry    schedule 09.03.2017
comment
Пожалуйста, опубликуйте код, в котором вы вызываете save() . Я не вижу никаких журналов, говорящих о новой транзакции, созданной WebSphereUowTransactionManager.   -  person Sergii Shevchyk    schedule 10.03.2017
comment
``` @Component public class DataExchangeLogicWrapper { @Autowired private DataExchangeLogic exchange; публичный дескриптор ReturnType (ParameterType pt) { return exchange.handle (pt); } } //аннотации public class UserCase { @Autowired private DataExchangeLogic exchange; публичный дескриптор ReturnType (ParameterType pt) { return exchange.handle (pt); } } ``` =› ``` //аннотации public class UserCase { @Autowired private DataExchangeLogicWrapper exchange; публичный дескриптор ReturnType (ParameterType pt) { return exchange.handle (pt); } } ```   -  person Gemini Keith    schedule 10.03.2017
comment
Извините за плохой предварительный просмотр кода. Вставьте эту информацию в свою IDE и посмотрите, в чем разница. Мое предложение - попытаться получить доступ к dao с помощью его оболочки, которая фактически обращается к методам в dao вместо того, чтобы вы напрямую вызывали их. Таким образом, вы можете убедиться, что другие фреймворки не приведут к аннулированию @Transactional. Кстати, если бы вы могли опубликовать свой исходный код, это принесло бы больше пользы.   -  person Gemini Keith    schedule 10.03.2017
comment
@shevchyk ... Скоро выложу   -  person Harry    schedule 10.03.2017
comment
@GeminiKeith ... Я реализую ваши изменения ...... Итак, я предполагаю, что Контроллер --> Логика обслуживания --> Оболочка --> DAO, верно? Почему ServiceLogic внедряется в оболочку? Должен ли контроллер вызывать оболочку?   -  person Harry    schedule 10.03.2017
comment
@shevchyk ... Отредактировал сообщение, чтобы добавить соответствующий код ... пожалуйста, дайте знать, если я все еще что-то пропустил   -  person Harry    schedule 10.03.2017
comment
Почему журнал сначала показывает UnexpectedRollbackException из транзакции JMS, а затем журналы SQL? Разве нет соответствующих журналов после транзакций SQL? Пожалуйста, добавьте и это. Если вы получаете UnexpectedRollbackException после транзакций SQL, это ожидается. Это также означает, что Spring DAO и JMS используют отдельные физические транзакции, даже если они являются одной и той же логической транзакцией.   -  person Shankar P S    schedule 10.03.2017
comment
Вы не открываете новую транзакцию, поэтому вставка выполняется без транзакции   -  person Sergii Shevchyk    schedule 11.03.2017
comment
Да, ты прав. Несколько дней назад я столкнулся с той же проблемой, вызванной моей инфраструктурой RPC, и @Transactional не работает. Так что это моя идея. Почему инъекция, потому что мне нужно, чтобы @Transactional действовало. Предположим, что вы используете Controller -> Service -> Dao, проблем быть не должно. Должно быть что-то еще. Я думаю, что мой ответ здесь не решит вашу проблему. Исходный код может помочь, если вы не против опубликовать его. В противном случае трудно сказать, что происходит не так.   -  person Gemini Keith    schedule 13.03.2017
comment
@GeminiKeith..Спасибо. Я думаю, что добавил весь соответствующий исходный код, за исключением, возможно, файла конфигурации spring в формате xml. Не могли бы вы сообщить, какой код я могу добавить, чтобы помочь решить проблему.   -  person Harry    schedule 13.03.2017


Ответы (3)


Поместите конфигурацию transactionManager и tx:annotation-driven в контекст root spring.

введите здесь описание изображения

Правило: Root context can see all the beans which Spring created. Child context(any Web Context) can see only its own beans.

В этом конкретном случае tx:annotation-driven ищет bean-компоненты с аннотацией @Transactional в веб-контексте. Он не может ничего найти, потому что вы определили dataExchLogic в корневом контексте. Вот почему у вас не было никакого транзакционного поведения.

@EnableTransactionManagement и ищет @Transactional только в bean-компонентах в том же контексте приложения, в котором они определены. Это означает, что, если вы помещаете конфигурацию, управляемую аннотациями, в WebApplicationContext для DispatcherServlet, она проверяет только @Transactional bean-компоненты в ваших контроллерах, а не ваши услуги. Дополнительную информацию смотрите в Разделе 21.2, «Сервлет DispatcherServlet».

Решение подразумевает перемещение tx:annotation-driven в корневой контекст, поскольку Root Context может найти любой bean-компонент, определенный либо в корневом, либо в любом веб-контексте.

person Sergii Shevchyk    schedule 10.03.2017
comment
Ух ты!. Я думаю, что это могло решить мою проблему. У меня больше нет новых строк, вставленных в БД. Но все еще есть проблема с откатом JMS - person Harry; 13.03.2017
comment
Гарри, ваш вопрос касался отката транзакции. Если хотите поднять новый - просто напишите его - person Sergii Shevchyk; 16.03.2017

Цитата из весенней документации:

Вы можете поместить аннотацию @Transactional перед определением интерфейса, методом интерфейса, определением класса или общедоступным методом класса. Однако простого наличия аннотации @Transactional недостаточно, чтобы активировать транзакционное поведение. Аннотация @Transactional — это просто метаданные, которые могут использоваться некоторой инфраструктурой среды выполнения, поддерживающей @Transactional, и которая может использовать метаданные для настройки соответствующих bean-компонентов с транзакционным поведением. В предыдущем примере элемент включает транзакционное поведение.

Что значит,

void create(DataExch dataExch);

должно быть

public void create(DataExch dataExch);

@Transactional поведение аннотации не отображается, если оно не применяется к общедоступному методу.

ИЗМЕНИТЬ:

Поскольку мой ответ был отклонен, чтобы поддержать мой ответ и пролить свет на транзакционное поведение, когда аннотированный метод Transactional вызывает метод без аннотации, взгляните на это:

метод @Transactional, вызывающий другой метод без аннотации @Transactional? в частности ответ Аруна П. Джони

person ritesh.garg    schedule 09.03.2017
comment
@Transactional определяется на сервисном уровне - person Sergii Shevchyk; 10.03.2017
comment
Это правда, но операции mybatis происходят в методе дао. Чтобы этот метод участвовал в транзакционном поведении, он должен соблюдать транзакционные требования. - person ritesh.garg; 10.03.2017
comment
нет необходимости быть общедоступным для create(), потому что транзакция открывается перед сохранением на сервисном уровне - person Sergii Shevchyk; 10.03.2017
comment
@ritesh.garg — Никаких улучшений/изменений при изменении видимости метода для публики - person Harry; 10.03.2017

Я думаю о двух возможностях. 1) Ваш класс DAO начинает новую транзакцию. 2) Ваш класс DAO не участвует в транзакции.

Я не вижу другой причины, по которой данные должны быть обновлены в базе данных. Можете ли вы добавить свойство ниже в log4j, чтобы увидеть, сколько транзакций запускается.

log4j.logger.org.springframework.transaction.interceptor = trace

Также syosut приведенный ниже статус транзакции в сервисе и методе DAO, чтобы увидеть, активна ли транзакция.

TransactionSynchronizationManager.isActualTransactionActive()

Дайте нам знать, что происходит.

person VimalKumar    schedule 11.03.2017
comment
Они оба печатают верно - person Harry; 13.03.2017
comment
Похоже, класс DAO запускает новую транзакцию. что вы видите в журнале для log4j.logger.org.springframework.transaction.interceptor = trace . Запускает две транзакции или только одну. - person VimalKumar; 13.03.2017
comment
Мы используем logback.xml. можно ли добавить туда ту же строку? - person Harry; 13.03.2017
comment
Добавьте это и посмотрите. ‹имя регистратора=org.springframework.transaction.interceptor level=TRACE/› - person VimalKumar; 13.03.2017