<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
首先我先宣告一點,本文單純就是技術探討,要從實際應用中來說的話,我並不建議這樣去玩分散式事務、也不建議這樣去玩多資料來源,畢竟分散式事務主要還是用在微服務場景下。
好啦,那就不廢話了,開整。
首先我們來梳理一下思路。
在上篇文章中,我們是一個微服務,在 A 中分別去呼叫 B 和 C,當 B 或者 C 有一個執行失敗的時候,就去回滾。B 和 C 都是呼叫遠端的服務,所謂的回滾也不是傳統意義上的資料庫回滾,而是一種“反向補償”,即利用一條更新 SQL,將已經更新的資料復原。在這個例子中,B 和 C 都是遠端服務,操作的也都是不同的資料庫,這不就是我們多資料來源中的情況麼!
在微服務中,一個服務實際上就代表了一個資料來源,而在我們多資料來源的案例中,一個註解就能標記出來一個資料來源,這樣一類比,你就會發現利用分散式事務來解決多資料來源中的事務問題其實是非常 Easy 的。而且這裡還不是微服務專案,只是一個單體專案,更簡單!
不過也有一些需要注意的細節。
接下來我們就結合程式碼來講講。
首先多資料來源的案例我就不重複寫了,我們之前已經寫過一個,這裡就不再贅述,文章一開頭也有相關的連結,還沒看過的小夥伴可以先看看。
也可以直接在公眾號後臺回覆 dynamic_datasource 獲取相關的案例。
因為上篇文章我主要是和大家分享的 seata 的 AT 模式,所以本文也是一樣,就先採用 AT 模式。
小夥伴們知道,在我們的多資料來源案例中,我們用到了兩個庫,test08 和 test09,現在也還是這兩個庫,但是現在由於我們使用的是 AT 模式,我們需要在這兩個庫中分別建立 undo log 表,用來記錄我們對錶的更新操作,當事務提交之後,undo log 表中的資料就會被清除,undo log,undo log 表的指令碼如下:
CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
資料庫準備好之後,接下來就是準備依賴了,seata 有兩個依賴,一個是 seata-all,還有一個微服務版的,咱們這裡就直接使用上篇文章中所用到的微服務版的,依賴如下:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2.2.2.RELEASE</version> </dependency>
配好之後,接下來提供兩個組態檔 file.conf 和 regsigry.conf,這兩個組態檔和上篇文章中介紹到的一模一樣,這裡不再贅述。
接下來設定 application.yaml,如下:
spring: cloud: alibaba: seata: tx-service-group: my_test_tx_group main: allow-circular-references: true seata: enable-auto-data-source-proxy: false application-id: dd
大家看下這裡的幾個設定:
好啦,這個檔案就設定好了。
接下來就是資料來源問題了,剛剛說了,seata 中會自動代理資料來源,用到的代理物件是 DataSourceProxy,而我們在之前自定義的資料來源載入中,並沒有用到這個 DataSourceProxy 物件所以這裡要稍作修改,一共改兩個地方,如下:
@Component @EnableConfigurationProperties(DruidProperties.class) public class LoadDataSource { @Autowired DruidProperties druidProperties; public Map<String, DataSourceProxy> loadAllDataSource() { Map<String, DataSourceProxy> map = new HashMap<>(); Map<String, Map<String, String>> ds = druidProperties.getDs(); try { Set<String> keySet = ds.keySet(); for (String key : keySet) { DataSource dataSource = druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))); DataSourceProxy proxyDs = new DataSourceProxy(dataSource); map.put(key, proxyDs); } } catch (Exception e) { e.printStackTrace(); } return map; } }
其實這裡的改動就是把之前的 DataSource 用 DataSourceProxy 重新包裹一下,然後將獲取到的 DataSourceProxy 存起來。最後再修改一下動態資料來源的地方:
@Component public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(LoadDataSource loadDataSource) { //1.設定所有的資料來源 Map<String, DataSourceProxy> allDs = loadDataSource.loadAllDataSource(); super.setTargetDataSources(new HashMap<>(allDs)); //2.設定預設的資料來源 //將來,並不是所有的方法上都有 @DataSource 註解,對於那些沒有 @DataSource 註解的方法,該使用哪個資料來源? super.setDefaultTargetDataSource(allDs.get(DataSourceType.DEFAULT_DS_NAME)); //3 super.afterPropertiesSet(); } /** * 這個方法用來返回資料來源名稱,當系統需要獲取資料來源的時候,會自動呼叫該方法獲取資料來源的名稱 * @return */ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
Map 中的 value 型別變為 DataSourceProxy,其他都不變。
另外還有一個地方要改造下,就是解析 @DataSource 註解的切面,在之前的解析中,我們是將異常捕獲了,現在我們要將之丟擲來,如下:
@Around("pc()") public Object around(ProceedingJoinPoint pjp) throws Throwable { //獲取方法上面的有效註解 DataSource dataSource = getDataSource(pjp); if (dataSource != null) { //獲取註解中資料來源的名稱 String value = dataSource.value(); DynamicDataSourceContextHolder.setDataSourceType(value); } try { return pjp.proceed(); } finally { DynamicDataSourceContextHolder.clearDataSourceType(); } }
將之丟擲來的原因也很簡單,因為這是切面方法,所有的 service 層方法都在這裡執行,如果將異常捕獲了,將來 service 層方法不丟擲異常,事務就沒法生效了。
好了,現在準備工作就算是到位了。
接下來我們寫一個簡單的多資料來源事務的案例,首先我們來建立一個 MasterService,專門用來操作 master 資料來源:
@Service public class MasterService { @Autowired MasterMapper masterMapper; @DataSource("master") public void addUser(String username, Integer age) { masterMapper.addUser(username, age); } }
mapper 就不用看了吧,就是普通的新增,大家可以在文末下載本文案例案例。
再來一個 SlaveService,用來操作 slave 資料來源:
@Service public class SlaveService { @Autowired SlaveMapper slaveMapper; @DataSource("slave") public void addAccount(String name, Double balance) { int i = 1 / 0; slaveMapper.addAccount(name, balance); } }
slave 資料來源的方法中有一個異常。
最後,我們在 UserService 中分別呼叫這兩個方法:
@Service public class UserService { @Autowired MasterService masterService; @Autowired SlaveService slaveService; @GlobalTransactional(rollbackFor = Exception.class) public void test() { masterService.addUser("javaboy.org", 99); slaveService.addAccount("javaboy.org", 99.0); } }
注意,test 方法上有一個全域性事務註解。
好啦,齊活!現在我們去執行這個 test 方法,由於 slaveService#addAccount 中的方法會丟擲異常,所以會導致整個事務回滾,最終的結果就是 master 中也沒有新增進資料。
好啦,結合上一篇文章,相信大家應該能夠熟練的使用 seata 分散式事務中的 at 模式了吧!
到此這篇關於Spring Boot 多資料來源處理事務的思路詳解的文章就介紹到這了,更多相關Spring Boot 多資料來源處理事務內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45