Вопрос или проблема
Я недавно мигрировал веб-приложение на базе Java и столкнулся с проблемами транзакций после следующих обновлений:
JDK: с 8 на 11
Spring: с 4.3.30.RELEASE на 5.1.20.RELEASE
Hibernate: с 5.2.13.Final на 5.4.33.Final
После миграции некоторые методы сервиса, аннотированные @Transactional, больше не инициируют транзакции, что приводит к следующей ошибке:
javax.persistence.TransactionRequiredException: нет активной транзакции
Вот пример метода сервиса, который не может инициировать транзакцию:
java
@Service
public class TicketServiceImpl {
public TicketServiceImplV2() {
System.out.println("TicketServiceImplV2 создан: " + this);
}
@Autowired
private TicketUpdatePersistence ticketPersistence;
@Transactional
public void createTicketUpdate(TicketUpdate update) {
Ticket ticket = ticketPersistence.findById(update.getTicket().getId())
.orElseThrow(() -> new EntityNotFoundException("Билет не найден"));
System.out.println("Транзакция активна: " + TransactionSynchronizationManager.isActualTransactionActive());
Ticket lockedTicket = ticketPersistence.findOneForUpdate(ticket.getId())
.orElseThrow(() -> new BadRequestException("Нет билета для обновления"));
}
}
Проблема: TransactionSynchronizationManager.isActualTransactionActive() возвращает false, и возникает исключение TransactionRequiredException при попытке обновить билет в базе данных.
Что я попробовал:
Конфигурация транзакций:
Есть только один лог “TicketServiceImplV2 создан”, что предполагает, что ваш сервис создается только в одном контексте (вероятно, в основном контексте), а не в обоих контекстах.
Я проверил, что менеджер транзакций правильно настроен:
xml
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
Публичный метод: Метод является публичным, а @Transactional правильно применен к нему.
Нет самовызовов: Я убедился, что нет самовызова метода @Transactional, так как он вызывается извне.
Механизм блокировки: Уровень персистенции использует пессимистичную блокировку в методе findOneForUpdate:
java
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT t FROM Ticket t WHERE t.id = :id")
Ticket findOneForUpdate(@Param("id") String ticketId);
мой файл конфигурации db.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd">
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" primary="true">
<property name="jdbcUrl" value="${db.url}" />
<property name="username" value="#{props.getDbUsername()}" />
<property name="password" value="#{props.getDbPassword()}" />
<property name="driverClassName" value="${db.driverClassName}" />
<property name="maximumPoolSize" value="${db.tomcat.maxActive}" />
<property name="minimumIdle" value="${db.tomcat.minIdle}" />
<property name="leakDetectionThreshold" value="${db.leak-detection-threshold}" />
<property name="connectionTimeout" value="${db.connection-timeout}" />
<property name="idleTimeout" value="${db.idle-timeout}" />
<property name="autoCommit" value="true" />
<property name="maxLifetime" value="1800000" />
</bean>
<bean name="quartzDS" class="com.zaxxer.hikari.HikariDataSource" primary="false">
<property name="jdbcUrl" value="#{props.getDbQuartzUrl()}"/>
<property name="username" value="#{props.getDbUsername()}" />
<property name="password" value="#{props.getDbPassword()}" />
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="maximumPoolSize" value="${db.tomcat.maxActive}" />
<property name="minimumIdle" value="${db.tomcat.minIdle}" />
<property name="leakDetectionThreshold" value="${db.leak-detection-threshold}" />
<property name="connectionTimeout" value="${db.connection-timeout}" />
<property name="idleTimeout" value="${db.idle-timeout}" />
</bean>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
id="entityManagerFactory">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceUnitName" value="spring-jpa"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false"/>
<property name="generateDdl" value="true"/>
</bean>
</property>
<property name="packagesToScan">
<array>
<value>com.quicktech.quicktech.common.model</value>
</array>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="dataSource" ref="dataSource"/>
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven />
<jpa:repositories base-package="com.quicktech.quicktech.common.persistence"
entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"
repository-impl-postfix="CustomImpl"/>
</beans>
моя общая конфигурация Spring XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<context:annotation-config/>
<context:property-placeholder location="classpath:/application.properties,classpath:/spring.properties"/>
<context:component-scan base-package="com.quicktech.quicktech.common"/>
<context:component-scan base-package="com.quicktech.quicktech.aspect"/>
<aop:aspectj-autoproxy />
</beans>
Мои вопросы:
Что может быть причиной того, что транзакции не активируются после миграции с Spring 4.3 на 5.1 и Hibernate 5.2 на 5.4?
Есть ли известные изменения в Spring 5 или Hibernate 5.4, которые могут повлиять на поведение управления транзакциями?
Может ли эта проблема быть связана с миграцией с JDK 8 на JDK 11, и если да, как мне это решить?
Я был бы признателен за любые предложения или шаги по устранению неполадок для решения этой проблемы. Заранее спасибо!
Дополнительная информация:
База данных: PostgreSQL 12
Версия Spring: 5.1.20.RELEASE
Версия Hibernate: 5.4.33.Final
Версия JDK: 11.0.24
Сервер приложений: Apache Tomcat 9.0.55
Ответ или решение
Проблема, с которой вы столкнулись, связана с тем, что после миграции на более новые версии JDK, Spring и Hibernate у вас больше не активируются транзакции, что приводит к возникновению исключения javax.persistence.TransactionRequiredException: no transaction is in progress
. Давайте проведем подробный анализ и разберемся в возможных причинах и решениях.
Возможные причины проблемы:
-
Изменения в конфигурации Spring:
- При миграции с Spring 4.3.30 до 5.1.20 произошли изменения в пространстве имен и в конфигурации XML. Убедитесь, что все XML-файлы настроены правильно на соответствие новым версиям. Например, проверьте, что у вас есть корректные схемы для
spring-tx
,spring-jpa
и других используемых вами пространств имен.
- При миграции с Spring 4.3.30 до 5.1.20 произошли изменения в пространстве имен и в конфигурации XML. Убедитесь, что все XML-файлы настроены правильно на соответствие новым версиям. Например, проверьте, что у вас есть корректные схемы для
-
Аспекты и аспектно-ориентированное программирование:
- Убедитесь, что ваш класс
TicketServiceImpl
корректно обрабатывается аспектом. Проверьте, что аннотация@Transactional
действительно применяется на уровне класса или метода, и нет конфликтов, из-за которых транзакции могут не инициироваться. - Поскольку у вас включен
<aop:aspectj-autoproxy />
, это должно работать, однако посмотрите на правильность использования аспектов в зависимости от новой конфигурации Spring.
- Убедитесь, что ваш класс
-
Проблемы с конфигурацией JPA:
- Убедитесь, что
JpaTransactionManager
настроен на использование правильногоEntityManagerFactory
и чтоdataSource
корректно инициализируется. Т.к. вы используете HikariCP, убедитесь, что используемые свойства в конфигурации проверки корректны и соединение действительно устанавливается.
- Убедитесь, что
-
Проблемы с управлением соединениями:
- Убедитесь, что для вашего Datasource установлены соответствующие настройки, особенно
autoCommit
. В вашем случае процесс коммита на уровне DB может мешать транзакциям Spring, если он установлен вtrue
.
- Убедитесь, что для вашего Datasource установлены соответствующие настройки, особенно
-
Влияние миграции на JDK:
- Хотя переход с JDK 8 на 11 не должен напрямую влиять на работу транзакций, при миграции могут возникнуть другие проблемы, связанные с библиотеками или механизмами работы приложения. Убедитесь, что зависимости вашего проекта полностью совместимы с JDK 11.
Рекомендации по устранению проблемы:
-
Убедитесь в корректной конфигурации XML:
- Проверьте все
xsi:schemaLocation
для соответствия текущим версиям Spring и удалите устаревшие ссылки. Попробуйте обновить их на более актуальные.
- Проверьте все
-
Включите отладку транзакций:
- Добавьте в конфигурацию журналирования (log4j/logback) подробную информацию по транзакциям. Это поможет отследить, когда и почему транзакции не инициализируются, выполнив соответствующие логи в классе
TicketServiceImpl
.
- Добавьте в конфигурацию журналирования (log4j/logback) подробную информацию по транзакциям. Это поможет отследить, когда и почему транзакции не инициализируются, выполнив соответствующие логи в классе
-
Проверьте настройки подключения:
- Проверьте, как ваше приложение устанавливает соединение с базой данных. Убедитесь, что соединение не закрывается или не теряется до вызова методов репозитория.
-
Обновите зависимости:
- Просмотрите ваши зависимости, возможно, их стоит обновить или проверить на конфликты. Убедитесь, что все необходимые библиотеки совместимы и работают, как ожидается.
Заключение:
Если после выполнения вышеуказанных предложений проблема все еще сохраняется, я рекомендую создать простую тестовую конфигурацию приложения, в которой будет минимальный набор классов и методов, и проверить, сохраняются ли транзакции. Также рассмотреть возможность использования акторов тестирования, чтобы лучше отследить поведение транзакционного менеджера.
Надеюсь, эти рекомендации помогут вам решить проблему с транзакциями в вашем приложении. Если у вас есть дальнейшие вопросы или необходима дополнительная помощь, не стесняйтесь обращаться!