![](/img/trans.png)
[英]Does a Spring @Transactional annotated method overwrite an @Transactional(readOnly=true) method if called within it?
[英]Spring opens a new transaction for each JpaRepository method that is called within an @Transactional annotated method
我有一個用@Transactional
注釋的方法。 這應該意味着在此方法中觸發的任何數據庫查詢都應該使用相同的事務。 但實際上這不會發生。 確實發生的是為方法本身打開了一個事務,但是當調用第一個JpaRepository
方法時,為該特定方法調用打開了一個新事務。
更復雜的是,對於自定義存儲庫方法,僅當JpaRepository
或JpaRepository custom method
也使用@Transactional
注釋時,才會打開此新事務。 如果沒有,我會得到以下關於它的跟蹤日志語句:
無需為 [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findFirstByIdNotNull] 創建事務:此方法不是事務性的。
所以它不會創建一個新的事務,但它似乎也沒有使用調用方法創建的事務。
這是存儲庫 class:
@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
。 hibernate 不支持NESTED
,因此這會導致錯誤,即使我在事務管理器上將 nestedTransactionAllowed 設置為true
,此錯誤仍然存在。 設置SUPPORTS
被忽略。 存儲庫仍然為每個調用的方法啟動一個新事務。 (更新: Propagation.MANDATORY
也沒有效果)customTransactionManager
,並將其作為參數添加到 @EnableJpaRepositories,如下所示: @EnableJpaRepositories(basePackages={"DAOs"}, transactionManagerRef = "customTransactionManager")
enableDefaultTransactions
的@EnableJpaRepositories
設置為false
。 這會導致findAll()
和save()
等默認方法不再在默認情況下在事務中執行。 但是,它不會強制他們使用帶有@Transactional
注釋的調用方法的事務。所以我的問題是:我如何使(自定義)jpa 存儲庫使用由調用方法啟動的事務?
編輯:這里JPA - 跨越多個 JpaRepository 方法調用的事務描述了類似的問題。 根據用戶 spring 的說法,當存儲庫實現Repository
而不是CrudRepository
或JpaRepository
時,僅使用現有事務。 但這是一種解決方法。
編輯 2:當我刪除@EnableTransactionManagement
時,我的 @Transactional 注釋繼續工作。 根據這篇文章,當我使用spring-boot-starter-jdbc
或spring-boot-starter-data-jpa as a dependency
時可能發生,我這樣做了。 這些依賴項會以某種方式干擾事務管理器的正常工作嗎?
這是我試圖理解您的問題的嘗試。 我建議啟用額外的調試
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——你會看到“不需要創建事務”——這是因為盡管默認的存儲庫實現“SimpleJpaRepository”被標記為事務性——只讀方法沒有被標記為事務性的。
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
在嘗試了不同的事情后,包括使用TransactionTemplate
我已經解決了以下解決方案:
首先,我通過使用以下注釋configuration
類關閉了 jparepository 方法的默認事務策略:
@EnableJpaRepositories(enableDefaultTransactions = false)
enableDefaultTransactions = false
會導致enableDefaultTransactions = false
任何繼承方法在調用它們時停止創建事務。 只有使用@Transactional
顯式注釋的 jpa 方法在調用時才會繼續創建新事務。
所有其他的現在將使用由調用方法啟動的任何事務,例如用@Transactional
注釋的服務方法。
但這並不明顯,因為對於未使用 @Transactional 顯式注釋的任何 jpa 方法,仍會生成This method is not transactional
日志跟蹤消息。 這可能有點令人困惑。
但是,我已經證明這些方法確實通過使用以下自定義更新方法對其進行測試來使用調用方法的事務。
@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)
對其進行顯式注釋。 要注意的另一件事是,任何調用方法都必須始終使用 @Transactional 進行注釋,否則 jpa 方法將在事務之外運行。
不過,我認為優勢遠遠超過這些小劣勢。 因為當為每個 jpa 方法調用創建一個新事務時,在性能方面成本非常高。 所以這是我現在要解決的解決方案。
要測試您自己的交易,您需要將此添加到您的 application.properties
logging.level.org.springframework.transaction.interceptor=TRACE
如果設置不起作用,請將Log4j2
添加到您的項目中。
在擁有多個數據源時遇到了同樣的問題,因此有多個事務管理器。 顯然問題在於標記為@Transactional
的服務方法使用了主事務管理器,而存儲庫被配置為使用自定義事務管理器:
@EnableJpaRepositories(
basePackageClasses = {
MyRepository.class
},
entityManagerFactoryRef = "customEntityManager",
transactionManagerRef = "customTransactionManager"
)
解決了在服務方法上使用 spring 的注釋並指定transactionManager
參數的問題@Transactional(transactionManager = "unaTransactionManager")
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.