@Transactional方法内JpaRepository未复用外层事务该如何解决
问题背景
我有一个标注了@Transactional的方法,按常理该方法内触发的所有数据库查询都应该使用同一个事务,但实际运行并未符合预期:方法本身成功开启了事务,但调用第一个JpaRepository方法时,就会为该方法单独开启新事务。
更复杂的是,自定义Repository方法只有自身也标注@Transactional时才会开启新事务,否则会输出如下Trace日志:
No need to create transaction for
[org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByIdNotNull]:
This method is not transactional.
此时既不会创建新事务,也不会复用调用方方法创建的事务。
相关代码
Repository类代码
@Repository public interface LanguageDao extends JpaRepository<Language, Long> { @Transactional public Language findByLanguageCode(String languageCode); public Language findByIdNotNull(); }
业务调用方法代码
@Transactional public void afterSingletonsInstantiated() { languageDao.findByLanguageCode(); //该自定义方法会开新事务,原因是自身标注了@Transactional languageDao.findAll(); //标准JpaRepository方法也会开新事务 languageDao.findByIdNotNull();//该自定义方法无@Transactional注解,不会开新事务 }
事务&JPA配置类代码
@EnableJpaRepositories(basePackages={"DAOs"}, transactionManagerRef = "customTransactionManager", enableDefaultTransactions = true) @EnableTransactionManagement @Configuration public class RootConfig implements InitializingBean { @Bean(name = "customTransactionManager") JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory); if (shouldCreateInitialLuceneIndex) { EntityManager entityManager = entityManagerFactory.createEntityManager(); createInitialLuceneIndex(entityManager); entityManager.close(); } return transactionManager; } }
application.properties配置
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.database-platform = org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.open-in-view = false
实际运行日志片段
第一行可确认afterSingletonsInstantiated方法的事务已正常创建:
[TRACE] 2021-11-08 15:32:40.811 [main] TransactionInterceptor - Getting transaction for [config.StartupChecks$$EnhancerBySpringCGLIB$$134b7631.afterSingletonsInstantiated] [INFO ] 2021-11-08 15:32:40.815 [main] StartupChecks - Calling sequence table reset procedure [DEBUG] 2021-11-08 15:32:40.833 [main] SQL - {call RESET_SEQUENCE_TABLE_VALUES_TO_LATEST_ID_VALUES()} [INFO ] 2021-11-08 15:32:41.087 [main] StartupChecks - Sequence tables reset call finished! [INFO ] 2021-11-08 15:32:41.087 [main] StartupChecks - doing stuff [INFO ] 2021-11-08 15:32:41.087 [main] StartupChecks - testing! [TRACE] 2021-11-08 15:32:41.087 [main] TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll] [DEBUG] 2021-11-08 15:32:41.088 [main] SQL - select language0_.id as id1_77_, language0_.dateCreated as datecrea2_77_, language0_.englishLanguageName as englishl3_77_, language0_.languageCode as language4_77_, language0_.rightToLeft as righttol5_77_, language0_.translatedLanguageName as translat6_77_ from languages language0_ [TRACE] 2021-11-08 15:32:41.091 [main] TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll] [INFO ] 2021-11-08 15:32:41.091 [main] StartupChecks - end test! [TRACE] 2021-11-08 15:32:41.091 [main] TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByLanguageCode] [DEBUG] 2021-11-08 15:32:41.112 [main] SQL - select language0_.id as id1_77_, language0_.dateCreated as datecrea2_77_, language0_.englishLanguageName as englishl3_77_, language0_.languageCode as language4_77_, language0_.rightToLeft as righttol5_77_, language0_.translatedLanguageName as translat6_77_ from languages language0_ where language0_.languageCode=? [TRACE] 2021-11-08 15:32:41.113 [main] TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByLanguageCode] [TRACE] 2021-11-08 15:32:41.113 [main] TransactionInterceptor - No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByIdNotNull]: This method is not transactional. [DEBUG] 2021-11-08 15:32:41.115 [main] SQL - select authority0_.ID as id1_7_, authority0_.dateCreated as datecrea2_7_, authority0_.NAME as name3_7_ from AUTHORITY authority0_ where authority0_.ID is not null limit ? [TRACE] 2021-11-08 15:32:41.120 [main] TransactionInterceptor - No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByIdNotNull]: This method is not transactional.
已尝试的无效方案
- 给
languageDao标注@Transactional(propagation = Propagation.SUPPORTS)或者@Transactional(propagation = Propagation.NESTED):NESTED不被Hibernate支持会报错,即便是给事务管理器设置nestedTransactionAllowed为true也无效;SUPPORTS配置被忽略,Repository调用时仍会开启新事务。补充:Propagation.MANDATORY也无效果。 - 将事务管理器命名为
customTransactionManager,并在@EnableJpaRepositories中配置transactionManagerRef = "customTransactionManager"。 - 将
@EnableJpaRepositories的enableDefaultTransactions设置为false:此时findAll()、save()等默认方法不再默认开启事务,但也不会复用调用方@Transactional标注方法开启的事务。
补充信息&疑问
- 有类似场景的开发者反馈,Repository直接继承
Repository而非CrudRepository/JpaRepository时Spring会复用现有事务,但这只是临时方案。 - 移除
@EnableTransactionManagement后@Transactional注解仍然生效,这是引入spring-boot-starter-jdbc或者spring-boot-starter-data-jpa依赖后的常见现象,不确定这些依赖是否会干扰事务管理器的正常工作。 - 如何让自定义和默认的JpaRepository方法都复用调用方开启的事务?
内容的提问来源于stack exchange,提问作者Maurice
相关产品推荐
相关产品推荐

