@Transactional和@DS怎樣在事務中切換數據源
@Transactional和@DS在事務中切換數據源
在一次需求中,需要對兩個數據庫進行讀寫操作,并且要保證對這兩個庫的操作的原子性。
所以就在一個service方法中加入了@Transaction,方法中包含了對兩個庫的操作。
spring: datasource: dynamic: primary: A datasource: A: url:.. driver-class-name: com.mysql.jdbc.Driver username: root password: B: url: .. driver-class-name: com.mysql.jdbc.Driver username: root password: C: url: .. driver-class-name: com.mysql.jdbc.Driver username: root password:
@Mapper @DS("A") public interface AMapper{ @Insert("insert into a ...") void save(); }
@Mapper @DS("B") public interface BMapper{ @Insert("insert into b ...") void save(); }
public class aService{ @Autowired private aMapper、bMapper、cMapper @Transactional public void save(){ aMapper.save(); bMapper.save(); #報錯 } }
在執(zhí)行測試時bMapper.save()發(fā)生了錯誤,報錯信息:找不到b表,很明顯sql是從A庫中找b表,當然會報錯找不到b表。
如果把@Transactional注解去掉就可以正常運行,數據也成功分別寫入兩個庫中。
但是我們命名已經在2個Mapper上加了@Ds()注解來切換要是用的數據源,那為什么加入了事務就報錯了呢?
@Transactional執(zhí)行流程
1、save方法添加了 @Transactional
注解,Spring 事務就會生效。此時,Spring TransactionInterceptor 會通過 AOP 攔截該方法,創(chuàng)建事務。而創(chuàng)建事務,勢必就會獲得數據源。
- 那么,TransactionInterceptor 會使用 Spring DataSourceTransactionManager 創(chuàng)建事務,并將事務信息通過 ThreadLocal 綁定在當前線程。
- 而事務信息,就包括事務對應的 Connection 連接。那也就意味著,還沒走到 OrderMapper 的查詢操作,Connection 就已經被創(chuàng)建出來了。
- 并且,因為事務信息會和當前線程綁定在一起,在 OrderMapper 在查詢操作需要獲得 Connection 時,就直接拿到當前線程綁定的 Connection ,而不是 OrderMapper 添加
@DS
注解所對應的 DataSource 所對應的 Connection 。
2、OK ,那么我們現(xiàn)在可以把問題聚焦到 DataSourceTransactionManager 是怎么獲取 DataSource 從而獲得 Connection 的了。
- 對于每個 DataSourceTransactionManager 數據庫事務管理器,創(chuàng)建時都會傳入其需要管理的 DataSource 數據源。
- 在使用
dynamic-datasource-spring-boot-starter
時,它創(chuàng)建了一個 DynamicRoutingDataSource ,傳入到 DataSourceTransactionManager 中。 - 而
DynamicRoutingDataSource
負責管理我們配置的多個數據源。例如說,本示例中就管理了a、b
兩個數據源,并且默認使用 a 數據源。 - 那么在當前場景下,DynamicRoutingDataSource 需要基于
@DS
獲得數據源名,從而獲得對應的 DataSource ,結果因為我們在 Service 方法上,并沒有添加 @DS 注解,所以它只好返回默認數據源,也就是 a 。故此,就發(fā)生了 找不到表 的異常。
我們在上面了解到,因為@Transactional會創(chuàng)建事務然后獲得數據源,因為我們service方法上沒有@DS注解,就拿了默認數據源,并且在這之后,這個事務信息會通過threadLocal跟當前線程綁定,事務信息包括了connection連接,也就意味著,在進入這個service方法的時候,當前事務就綁定了數據源a,在運行到bMapper.save()時,因為connection已經存在,所以拿到的數據源還是a,這時候就找不到b庫里的表了。
解決方法
把對B庫操作的方法都加上@DS和 @Transactional注解(在service上加)。
ps:在完成了aMapper.save()之后去調用bMapper.save()時,一定要把@Transactional設置為Propagation.REQUIRES_NEW
,這樣在調用另一個事務方法時,TransactionInterceptor 會將原事務掛起,暫時性的將原事務信息和當前線程解綁,然后創(chuàng)建一個新的事務,并且從數據源中取出一個connection鏈接,而此時的數據源已經被切換成我們需要的數據源。如果B操作發(fā)生了異常,B事務將回滾并將異常進行拋出到A,A事務自然也會回滾。
在對B庫的操作如果不需要事務的話,可以在對B庫操作的方法上指定事務的隔離級別為NOT_SUPPORTED
,這樣執(zhí)行到該方法時會暫停掛起當前事務,等待對B庫的方法執(zhí)行完畢再恢復事務。
pps:當前事務方法里用this來調用另一個事務方法時,當前這個事務方法的@DS也會起作用,原因是@Ds是基于AOP切面在方法執(zhí)行前切換數據源,而this調用的不是通過代理生成的事務對象,而是自己本身的原對象,不會開啟事務。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
如何利用postman完成JSON串的發(fā)送功能(springboot)
這篇文章主要介紹了如何利用postman完成JSON串的發(fā)送功能(springboot),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07Mybatis-plus:${ew.sqlselect}用法說明
這篇文章主要介紹了Mybatis-plus:${ew.sqlselect}用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06詳解Java中布隆過濾器(Bloom Filter)原理及其使用場景
布隆過濾器是1970年由布隆提出的,它實際上是一個很長的二進制向量和一系列隨機映射函數,它的作用是檢索一個元素是否存在我們的集合之中,本文給大家詳細的講解一下布隆過濾器,感興趣的同學可以參考閱讀2023-05-05