在整合MySQL,JDBCTemplate這篇中的結(jié)尾,我們成功的向兩個數(shù)據(jù)庫中分別添加了一條數(shù)據(jù)。但是我們思考一下,如果PetsServiceImpl.java中的savePets()中如果發(fā)生異常了會怎么樣呢? @Override@Transactionalpublic void savePets(Pets pets) { petsDAO.save(pets, familyJdbcTemplate); petsDAO.save(pets, family2JdbcTemplate); // 異常,分母不能為零 int num = 1/0; }123456789復(fù)制代碼類型:[java] 我們添加了@Transactional注解,按照正常的思路來說兩個數(shù)據(jù)庫中都不應(yīng)該有數(shù)據(jù)加入: 數(shù)據(jù)庫Family確實沒有數(shù)據(jù)加入: 但是Family2卻有數(shù)據(jù)加入: 這是不符合邏輯的,因為數(shù)據(jù)庫事物不能跨鏈接,數(shù)據(jù)源更不能跨庫。如果出現(xiàn)了上述操作那這個事務(wù)就變成了分布式事務(wù),需要一個統(tǒng)一協(xié)調(diào)的管理器。所以我們要解決這個問題。 JTA實現(xiàn)跨庫分布式事務(wù) XA規(guī)范:是一個兩階段提交協(xié)議(同時對數(shù)據(jù)進行處理),被很多數(shù)據(jù)庫和中間件支持。 兩階段提交協(xié)議(2PC):將整個事務(wù)分成準(zhǔn)備階段和提交階段兩個階段。2指是兩個階段,P(Prepare),C(Commit)。 正常情況下: 準(zhǔn)備階段:事務(wù)管理器(TM)給Family和Family2發(fā)送Prepare消息,F(xiàn)amily和Family2在本地執(zhí)行事務(wù),寫本地的Undo/Redo日志,但是此時事務(wù)沒有提交。 提交階段:事務(wù)管理器(TM)收到Family和Family2超時或執(zhí)行失敗的消息時,會對Family和Family2發(fā)送回滾消息。如果事務(wù)管理器沒有收到關(guān)于失敗的消息,就會發(fā)送提交消息。完成回滾或是提交之后需要釋放事務(wù)處理過程中使用的鎖資源。 成功情況: 失敗情況: 兩階段提交是在數(shù)據(jù)庫層面實現(xiàn)的,下面我們來結(jié)合Family和Family2出現(xiàn)的問題進行解析。 現(xiàn)在我們的應(yīng)用程序持有Family和Family2兩個數(shù)據(jù)庫,應(yīng)用程序通過事務(wù)管理器同時通知Family,F(xiàn)amily2添加數(shù)據(jù)。事務(wù)管理器收到執(zhí)行的回復(fù),兩個數(shù)據(jù)庫有一方失敗,就向另一方發(fā)起回滾事務(wù)?;貪L完畢,資源鎖釋放。如果都是成功,此時向所有數(shù)據(jù)庫發(fā)起提交事務(wù),提交完畢,資源鎖釋放。 XA規(guī)范缺點: 同步阻塞,數(shù)據(jù)庫鎖定資源時間太長,全局鎖,并發(fā)低,不適合長事務(wù)場景,還需要本地數(shù)據(jù)庫支持XA規(guī)范。 JTA規(guī)范:可以理解為XA規(guī)范的Java版本。 Atomikos:分布式事務(wù)管理器,JTA/XA的具體實現(xiàn)。(支持分布式事務(wù),單獨引入了事物管理器導(dǎo)致性能開銷大,不適合用于高并發(fā)場景) 分布式事務(wù)就是跨數(shù)據(jù)庫對數(shù)據(jù)進行操作的事務(wù),要達到的目的是要么都成功,要么都失敗。 首先我們在pom.xml文件中引入atomikos依賴包: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency>1234復(fù)制代碼類型:[java] 之后我們需要對application.yml進行修改: server: port: 8888spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 datasource: familydb: uniqueResourceName: family xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource xaProperties: url: jdbc:mysql://localhost:3306/Family?serverTimezone=GMT%2b8&characterEncoding=utf-8 username: root password: 123456 exclusiveConnectionMode: true minPoolSize: 5 maxPoolSize: 15 testQuery: SELECT 1 from dual family2db: uniqueResourceName: family2 xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource xaProperties: url: jdbc:mysql://localhost:3306/Family2?serverTimezone=GMT%2b8&characterEncoding=utf-8 username: root password: 123456 exclusiveConnectionMode: true minPoolSize: 5 maxPoolSize: 15 testQuery: SELECT 1 from dual12345678910111213141516171819202122232425262728293031復(fù)制代碼類型:[java] 現(xiàn)在再來改寫一下DataSourceConfig.java: @Configurationpublic class DataSourceConfig { // JTA數(shù)據(jù)源family @Bean @Primary @ConfigurationProperties(prefix = "familydb") public DataSource familyDataSource() { // 返回AtomikosDataSourceBean,配置屬性也都是注入到這個類里面 return new AtomikosDataSourceBean(); } // JTA數(shù)據(jù)源family2 @Bean @ConfigurationProperties(prefix = "family2db") public DataSource family2DataSource() { return new AtomikosDataSourceBean(); } // familyJdbcTemplate使用familyDataSource數(shù)據(jù)源 @Bean public JdbcTemplate familyJdbcTemplate( @Qualifier("familyDataSource") DataSource familyDataSource) { return new JdbcTemplate(familyDataSource); } // family2JdbcTemplate使用family2DataSource數(shù)據(jù)源 @Bean public JdbcTemplate family2JdbcTemplate( @Qualifier("family2DataSource") DataSource family2DataSource) { return new JdbcTemplate(family2DataSource); } }1234567891011121314151617181920212223242526272829303132復(fù)制代碼類型:[java] 將上述代碼改寫完成后,在config文件下創(chuàng)建TransactionManagerConfig.java: package com.javafamily.familydemo.config;import com.atomikos.icatch.jta.UserTransactionImp;import com.atomikos.icatch.jta.UserTransactionManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.DependsOn;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.jta.JtaTransactionManager;import javax.transaction.SystemException;import javax.transaction.TransactionManager;import javax.transaction.UserTransaction;@Configurationpublic class TransactionManagerConfig { @Bean public UserTransaction userTransaction() throws SystemException { UserTransactionImp userTransactionImp = new UserTransactionImp(); userTransactionImp.setTransactionTimeout(10000); return userTransactionImp; } @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close") public TransactionManager atomikosTransactionManager() throws Throwable { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(false); return userTransactionManager; } @Bean(name = "transactionManager") @DependsOn({"userTransaction", "atomikosTransactionManager"}) public PlatformTransactionManager transactionManager() throws Throwable { UserTransaction userTransaction = userTransaction(); JtaTransactionManager manager = new JtaTransactionManager(userTransaction, atomikosTransactionManager()); return manager; } }1234567891011121314151617181920212223242526272829303132333435363738394041復(fù)制代碼類型:[java] 以上的代碼屬于事務(wù)管理器配置,事務(wù)管理器負(fù)責(zé)協(xié)調(diào)多個JTA數(shù)據(jù)源實現(xiàn)事務(wù)機制,這是固定的寫法,不需要糾結(jié)。 完成了以上的配置之后,我們再次驗證分母為零的異常問題: 報出異常后,查看分別查看兩個數(shù)據(jù)庫: 兩個數(shù)據(jù)庫由于異常都沒有數(shù)據(jù)被插入,問題解決! |
|