您好,登錄后才能下訂單哦!
這篇“Spring Boot多數(shù)據(jù)源處理事務(wù)實例分析”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Spring Boot多數(shù)據(jù)源處理事務(wù)實例分析”文章吧。
首先我們來梳理一下思路。
在上篇文章中,我們是一個微服務(wù),在 A 中分別去調(diào)用 B 和 C,當 B 或者 C 有一個執(zhí)行失敗的時候,就去回滾。B 和 C 都是調(diào)用遠程的服務(wù),所謂的回滾也不是傳統(tǒng)意義上的數(shù)據(jù)庫回滾,而是一種“反向補償”,即利用一條更新 SQL,將已經(jīng)更新的數(shù)據(jù)復(fù)原。在這個例子中,B 和 C 都是遠程服務(wù),操作的也都是不同的數(shù)據(jù)庫,這不就是我們多數(shù)據(jù)源中的情況么!
在微服務(wù)中,一個服務(wù)實際上就代表了一個數(shù)據(jù)源,而在我們多數(shù)據(jù)源的案例中,一個注解就能標記出來一個數(shù)據(jù)源,這樣一類比,你就會發(fā)現(xiàn)利用分布式事務(wù)來解決多數(shù)據(jù)源中的事務(wù)問題其實是非常 Easy 的。而且這里還不是微服務(wù)項目,只是一個單體項目,更簡單!
不過也有一些需要注意的細節(jié)。
接下來我們就結(jié)合代碼來講講。
首先多數(shù)據(jù)源的案例我就不重復(fù)寫了
因為上篇文章我主要是和大家分享的 seata 的 AT 模式,所以本文也是一樣,就先采用 AT 模式。
小伙伴們知道,在我們的多數(shù)據(jù)源案例中,我們用到了兩個庫,test08 和 test09,現(xiàn)在也還是這兩個庫,但是現(xiàn)在由于我們使用的是 AT 模式,我們需要在這兩個庫中分別創(chuàng)建 undo log 表,用來記錄我們對表的更新操作,當事務(wù)提交之后,undo log 表中的數(shù)據(jù)就會被清除,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;
數(shù)據(jù)庫準備好之后,接下來就是準備依賴了,seata 有兩個依賴,一個是 seata-all,還有一個微服務(wù)版的,咱們這里就直接使用上篇文章中所用到的微服務(wù)版的,依賴如下:
<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
大家看下這里的幾個配置:
tx-service-group:這個是事務(wù)群組的名稱,相關(guān)名字是在 file.conf 中配置的。
allow-circular-references:這個是允許循環(huán)依賴,可能有的小伙伴已經(jīng)知道,現(xiàn)在最新版的 Spring Boot 中已經(jīng)禁掉了循環(huán)依賴,但是這個 seata 中似乎還是用到了循環(huán)依賴,所以要開啟。
enable-auto-data-source-proxy:由于 seata 會自動代理數(shù)據(jù)源,但是我們現(xiàn)在的數(shù)據(jù)源是自己加載的,所以關(guān)閉掉這個數(shù)據(jù)源的自動代理,將來用自己的。
application-id:給我們的應(yīng)用取一個名字。
好啦,這個文件就配置好了。
接下來就是數(shù)據(jù)源問題了,剛剛說了,seata 中會自動代理數(shù)據(jù)源,用到的代理對象是 DataSourceProxy,而我們在之前自定義的數(shù)據(jù)源加載中,并沒有用到這個 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 存起來。最后再修改一下動態(tài)數(shù)據(jù)源的地方:
@Component public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(LoadDataSource loadDataSource) { //1.設(shè)置所有的數(shù)據(jù)源 Map<String, DataSourceProxy> allDs = loadDataSource.loadAllDataSource(); super.setTargetDataSources(new HashMap<>(allDs)); //2.設(shè)置默認的數(shù)據(jù)源 //將來,并不是所有的方法上都有 @DataSource 注解,對于那些沒有 @DataSource 注解的方法,該使用哪個數(shù)據(jù)源? super.setDefaultTargetDataSource(allDs.get(DataSourceType.DEFAULT_DS_NAME)); //3 super.afterPropertiesSet(); } /** * 這個方法用來返回數(shù)據(jù)源名稱,當系統(tǒng)需要獲取數(shù)據(jù)源的時候,會自動調(diào)用該方法獲取數(shù)據(jù)源的名稱 * @return */ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
Map 中的 value 類型變?yōu)?DataSourceProxy,其他都不變。
另外還有一個地方要改造下,就是解析 @DataSource 注解的切面,在之前的解析中,我們是將異常捕獲了,現(xiàn)在我們要將之拋出來,如下:
@Around("pc()") public Object around(ProceedingJoinPoint pjp) throws Throwable { //獲取方法上面的有效注解 DataSource dataSource = getDataSource(pjp); if (dataSource != null) { //獲取注解中數(shù)據(jù)源的名稱 String value = dataSource.value(); DynamicDataSourceContextHolder.setDataSourceType(value); } try { return pjp.proceed(); } finally { DynamicDataSourceContextHolder.clearDataSourceType(); } }
將之拋出來的原因也很簡單,因為這是切面方法,所有的 service 層方法都在這里執(zhí)行,如果將異常捕獲了,將來 service 層方法不拋出異常,事務(wù)就沒法生效了。
好了,現(xiàn)在準備工作就算是到位了。
接下來我們寫一個簡單的多數(shù)據(jù)源事務(wù)的案例,首先我們來創(chuàng)建一個 MasterService,專門用來操作 master 數(shù)據(jù)源:
@Service public class MasterService { @Autowired MasterMapper masterMapper; @DataSource("master") public void addUser(String username, Integer age) { masterMapper.addUser(username, age); } }
mapper 就不用看了吧,就是普通的添加,大家可以在文末下載本文案例案例。
再來一個 SlaveService,用來操作 slave 數(shù)據(jù)源:
@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 數(shù)據(jù)源的方法中有一個異常。
最后,我們在 UserService 中分別調(diào)用這兩個方法:
@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 方法上有一個全局事務(wù)注解。
好啦,齊活!現(xiàn)在我們?nèi)?zhí)行這個 test 方法,由于 slaveService#addAccount 中的方法會拋出異常,所以會導(dǎo)致整個事務(wù)回滾,最終的結(jié)果就是 master 中也沒有添加進數(shù)據(jù)。
以上就是關(guān)于“Spring Boot多數(shù)據(jù)源處理事務(wù)實例分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。