我有一个用@Transactional
注释的方法。这意味着在此方法中触发的任何数据库查询都应该使用相同的事务。但在现实中,这种情况不会发生。所发生的事情是为该方法本身打开一个事务,但是当第一个JpaRepository
方法被调用时,将为该特定方法调用打开一个新事务。
为了使事情变得更复杂,对于自定义存储库方法,这个新的事务是只在或JpaRepository custom method
也用@Transactional
注释时打开的。如果没有,我会得到以下关于它的跟踪日志语句:
无需为org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByIdNotNull:创建事务--此方法不是事务性的。
因此,它不创建新事务,但似乎也不使用调用方法创建的事务。
下面是存储库类:
@Repository
public interface LanguageDao extends JpaRepository<Language, Long> {
@Transactional
public Language findByLanguageCode(String languageCode);
public Language findByIdNotNull();
}
下面是使用不同存储库方法的方法。
@Transactional
public void afterSingletonsInstantiated() {
languageDao.findByLanguageCode(); //This custom method opens a new transaction, but only because i've annotated this method with @Transactional as well.
languageDao.findAll(); //This one as well because its a standard JpaRepository method.
languageDao.findByIdNotNull();//This custom method doesn't because it lacks its own @Transactional annotation.
}
下面是@Configuration文件,启用了事务管理和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.SUPPORTS)或@Transactional(传播= Propagation.NESTED)的注释。hibernate不支持NESTED
,因此这会导致错误,即使在事务管理器上将nestedTransactionAllowed设置为true
时,此错误仍然存在。设置SUPPORTS
将被忽略。存储库仍然为每个被调用的方法启动一个新事务。(Update:Propagation.MANDATORY
也没有效果)我已经给事务管理器命名为customTransactionManager
,并将其作为参数添加到@EnableJpaRepositories中,如:将@EnableJpaRepositories
的enableDefaultTransactions
设置为false
。这将导致默认情况下不再在事务中执行默认方法,如findAll()
和save()
。但是,它并不强制它们使用使用@Transactional
.注释的调用方法的事务
因此,我的问题是:如何使(自定义) jpa存储库使用调用方法启动的事务?
编辑:在这里,JPA - Spanning a transaction over multiple JpaRepository method calls描述了类似的问题。根据用户的说法,spring只在存储库实现Repository
而不是CrudRepository
或JpaRepository
时才使用现有事务。但这是个解决办法。
编辑2:当我删除@EnableTransactionManagement
时,我的@Transactional注释继续工作。根据this post,当我使用spring-boot-starter-jdbc
或spring-boot-starter-data-jpa as a dependency
时,就会发生这种情况。这些依赖关系是否会以某种方式干扰事务管理器的正常工作?
发布于 2021-11-08 23:16:59
这是我理解你的问题的尝试。我建议启用额外的调试。
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
我的测试服务类--注意,这是标记为事务性的--就目前而言,这是我们打算将它放在这里的唯一地方--以创建事务边界。
@Service
public class LanguageService {
@Autowired
private LanguageRepository languageRepository;
@Transactional
public void runAllMethods() {
languageRepository.findByLanguageCode("en");
languageRepository.findAll();
languageRepository.findByIdNotNull();
}
}
接下来是存储库--没有事务注释。
public interface LanguageRepository extends JpaRepository<Language, Long> {
public Language findByLanguageCode(String languageCode);
public Language findByIdNotNull();
}
现在,在通过控制器访问服务时,我得到了下面的日志。请注意上面写着“创建具有名称com.shailendra.transaction_demo.service.LanguageService.runAllMethods: PROPAGATION_REQUIRED,ISOLATION_DEFAULT的新事务”的行--意思是在方法调用开始时创建了事务。
还请注意“参与现有事务”的语句,该语句指示该方法正在参与事务。
2021-11-09 11:43:06.061 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2084817241<open>)] for JPA transaction
2021-11-09 11:43:06.061 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [com.shailendra.transaction_demo.service.LanguageService.runAllMethods]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2021-11-09 11:43:06.069 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@3107a702]
2021-11-09 11:43:06.069 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : Getting transaction for [com.shailendra.transaction_demo.service.LanguageService.runAllMethods]
2021-11-09 11:43:06.099 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByLanguageCode]: This method is not transactional.
Hibernate: select language0_.id as id1_0_, language0_.date_created as date_cre2_0_, language0_.english_language_name as english_3_0_, language0_.language_code as language4_0_, language0_.right_to_left as right_to5_0_, language0_.translated_language_name as translat6_0_ from language language0_ where language0_.language_code=?
2021-11-09 11:43:06.333 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2084817241<open>)] for JPA transaction
2021-11-09 11:43:06.333 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2021-11-09 11:43:06.333 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
Hibernate: select language0_.id as id1_0_, language0_.date_created as date_cre2_0_, language0_.english_language_name as english_3_0_, language0_.language_code as language4_0_, language0_.right_to_left as right_to5_0_, language0_.translated_language_name as translat6_0_ from language language0_
2021-11-09 11:43:06.348 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
2021-11-09 11:43:06.348 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByIdNotNull]: This method is not transactional.
Hibernate: select language0_.id as id1_0_, language0_.date_created as date_cre2_0_, language0_.english_language_name as english_3_0_, language0_.language_code as language4_0_, language0_.right_to_left as right_to5_0_, language0_.translated_language_name as translat6_0_ from language language0_ where language0_.id is not null
2021-11-09 11:43:06.348 TRACE 24956 --- [nio-8181-exec-1] o.s.t.i.TransactionInterceptor : Completing transaction for [com.shailendra.transaction_demo.service.LanguageService.runAllMethods]
2021-11-09 11:43:06.348 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2021-11-09 11:43:06.348 DEBUG 24956 --- [nio-8181-exec-1] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(2084817241<open>)]
对于只读方法(如findAll ),您将看到“不需要创建事务”--这是因为尽管默认的Repository实现"SimpleJpaRepository“被标记为事务性,但只读方法并不是事务性的。
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
发布于 2021-11-08 16:56:25
在尝试了不同的东西(包括使用TransactionTemplate
)之后,我确定了以下解决方案:
首先,通过用以下方式注释configuration
类,关闭了jparepository方法的默认事务策略:
@EnableJpaRepositories(enableDefaultTransactions = false)
enableDefaultTransactions = false
会导致任何继承的JpaRepository方法在被调用时停止创建事务。只有使用@Transactional
显式注释的jpa方法在调用时才会继续创建新事务。
其他所有事务现在都将使用由调用方法启动的任何事务,例如使用@Transactional
注释的服务方法。
但这并不明显,因为This method is not transactional
日志跟踪消息仍将为未显式注释的任何jpa方法生成@Trans显式。这可能有点让人困惑。
但是,我已经证明了这些方法确实使用了调用方法的事务,使用下面的自定义更新方法对其进行了测试。
@Modifying
@Query("UPDATE User u SET u.userStatus = 1 WHERE u.userStatus = 0")
public void resetActiveUserAccountsToStatusOffline();
这样的方法需要有一个事务,否则抛出异常javax.persistence.TransactionRequiredException: Executing an update/delete query
。但是如您所见,这个jpa方法没有使用@Transactional
注释,因此它确实使用了调用服务方法启动的事务。
设置enableDefaultTransactions = false
有一个小缺点,那就是继承方法(如findAll
)的事务类型并不总是使用只读的事务。这实际上取决于服务级别事务是否是只读的。但是,您仍然可以重写findAll方法,并使用Transactional(readOnly = false)
对其进行明确的注释。要注意的另一件事是,任何调用方法都必须始终使用@ transaction注释,否则jpa方法将在事务外部运行。
不过,我认为优势远远大于这些小缺点。因为在为每个jpa方法调用创建一个新事务时,在性能上非常昂贵。所以这就是我现在要满足的解决方案。
要测试您自己的事务,需要将其添加到application.properties中
logging.level.org.springframework.transaction.interceptor=TRACE
如果设置不起作用,请将Log4j2
添加到项目中。
编辑:
当调用方法已经创建了一个JpaMethods时,这些由logical transactions
打开的附加事务仅为physical transaction
。这里有更多关于这一点的信息:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction这些jpa方法仍然使用调用方法创建的事务。
这个SO线程中的最后一个答案也很好地解释了逻辑事务和物理事务之间的区别:Difference between physical and logical transactions in spring
发布于 2022-08-10 06:00:13
在拥有多个数据源时,遇到了相同的问题,因此出现了多个事务管理器。显然,问题在于标记为@Transactional
的服务方法使用主事务管理器,而存储库配置为使用自定义事务管理器:
@EnableJpaRepositories(
basePackageClasses = {
MyRepository.class
},
entityManagerFactoryRef = "customEntityManager",
transactionManagerRef = "customTransactionManager"
)
使用spring对服务方法的注释解决了使用transactionManager
param指定的@Transactional(transactionManager = "customTransactionManager")
的问题
https://stackoverflow.com/questions/69885782
复制相似问题