Spring5→6、Hibernate5→6迁移后单元测试无法创建事务管理器
将运行在Tomcat 9.5上的Web应用迁移至Tomcat 10,已完成javax.*到jakarta.*的包重命名,基于Java 17编译,使用Spring 6.0.22 + Hibernate Core 6.3.5.Final,持久化单元测试无法正常运行。
当前Spring上下文配置(XML):
<beans> <bean id="persistenceDataSource" class="com.infogix.component.kernel.export.db.BasicDataSource" destroy-method="close" p:driverClassName="${db.driver}" p:url="${db.url}" p:username="${db.user}" p:password="${db.password}"/> <bean id="hibernateProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="properties"> <props> <prop key="hibernate.event.merge.entity_copy_observer">allow</prop> <prop key="hibernate.show_sql">${db.hibernate.show_sql}</prop> <prop key="hibernate.id.optimizer.pooled.preferred">org.hibernate.id.enhanced.PooledLoOptimizer</prop> <prop key="hibernate.id.new_generator_mappings">false</prop> <prop key="hibernate.id.generator.stored_last_used">false</prop> <prop key="hibernate.current_session_context_class">jta</prop> </props> </property> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="persistenceDataSource"/> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> </bean> </property> <property name="jpaProperties" ref="hibernateProperties"/> <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" /> </property> <property name="packagesToScan"> <list> <value>com.infogix.component.**.export.fdt.**</value> <value>com.infogix.component.**.rsc.impl.**</value> <value>com.infogix.component.datastore.ent.impl.**</value> </list> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/> <bean id="persistenceExceptionTranslationPostProcessor" class= "org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> </beans>
单元测试配置:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"/persistence-test-context.xml"})
错误信息:org.hibernate.HibernateException: No TransactionManagerLookup specified
核心情况:
- 单元测试为独立运行,不依赖Tomcat或其他容器
- 仅使用单个Postgres 10数据库,不使用JTA跨库事务
- 原Spring5+Hibernate5环境中,注入SessionFactory操作数据库
- 大量使用Spring的
@Transactional注解
疑问:
- 单元测试未在容器内运行,是否应该使用LocalContainerEntityManagerFactoryBean?这里的'Container'指完整Jakarta EE容器还是Tomcat这类Servlet容器?
- 若应使用该Bean,如何正确配置以创建事务管理器?
- 应该注入SessionFactory还是EntityManager?
1. 是否应该使用LocalContainerEntityManagerFactoryBean?
完全可以用。这里的"Container"指的是Spring容器,而非Jakarta EE完整容器或Tomcat这类Servlet容器。LocalContainerEntityManagerFactoryBean是Spring中最灵活的JPA工厂Bean,支持包扫描、自定义数据源、JPA属性配置等,不管是容器内还是独立环境(比如单元测试)都适用,尤其适合你这种依赖注解扫描实体类的场景。
2. 正确配置事务管理器的步骤
你的核心问题是Hibernate配置中指定了hibernate.current_session_context_class=jta,但又没有提供JTA事务管理器,导致报错。因为你不使用JTA,需要修改Hibernate配置,并调整Spring事务相关配置:
步骤1:修改Hibernate属性
删除hibernate.current_session_context_class=jta这一行(Spring会自动接管会话上下文,无需手动指定JTA相关配置)。修改后的hibernateProperties配置:
<bean id="hibernateProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="properties"> <props> <prop key="hibernate.event.merge.entity_copy_observer">allow</prop> <prop key="hibernate.show_sql">${db.hibernate.show_sql}</prop> <prop key="hibernate.id.optimizer.pooled.preferred">org.hibernate.id.enhanced.PooledLoOptimizer</prop> <prop key="hibernate.id.new_generator_mappings">false</prop> <prop key="hibernate.id.generator.stored_last_used">false</prop> <!-- 移除jta相关配置 --> </props> </property> </bean>
步骤2:确认JpaTransactionManager配置
你的JpaTransactionManager配置本身是正确的,它会自动适配本地事务(非JTA),只要EntityManagerFactory基于本地数据源配置,就能正常工作。无需引入Narayana或WildFly的JTA依赖,这些是多余的。
步骤3:调整LoadTimeWeaver(可选)
如果单元测试中不需要类加载时织入(比如没有使用JPA实体类增强),可以移除loadTimeWeaver配置,避免可能的类加载问题:
<!-- 移除这一段 --> <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" /> </property>
如果确实需要织入,确保测试启动时添加了JavaAgent参数(比如-javaagent:spring-instrument.jar)。
3. 注入SessionFactory还是EntityManager?
两种方式都支持,但推荐优先使用EntityManager,这是JPA的标准API,更符合Spring 6+和Hibernate 6的最佳实践。如果旧代码大量依赖SessionFactory,也可以通过EntityManager获取Hibernate Session,或者直接从EntityManagerFactory中提取SessionFactory:
方式1:注入EntityManager(推荐)
@PersistenceContext private EntityManager entityManager; // 需要时获取Hibernate Session Session session = entityManager.unwrap(Session.class);
方式2:注入SessionFactory
如果要保留原有代码习惯,可以在Spring配置中暴露SessionFactory Bean:
<bean id="sessionFactory" factory-bean="entityManagerFactory" factory-method="unwrap"> <constructor-arg value="org.hibernate.SessionFactory"/> </bean>
然后在代码中注入:
@Autowired private SessionFactory sessionFactory;
注意事项
不管用哪种方式,Spring的@Transactional注解都能正常生效,因为JpaTransactionManager同时支持EntityManager和SessionFactory的事务管理。
最终验证
修改配置后,单元测试应该能正常启动,@Transactional注解会自动管理本地数据库事务,无需JTA相关依赖。
内容的提问来源于stack exchange,提问作者Swavek L

