1、創建SpringBoot工程
工程名為my-tcc-demo 依賴如下

2、數據准備
134和129分別在user_134創建account_a表, user_129 創建account_b表

account_a表和account_b表數據結構時一致的。

默認數據如下圖所示

3、使用mybatis-generator生成相關文件
1) generatorConfig.xml 這個是連134的數據庫,生成成功后,在連129的數據庫
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--指定特定數據庫的jdbc驅動jar包的位置-->
<classPathEntry location="C:\Users\Think\Desktop\MyBatis\mybatis-generator-core-1.3.2\mybatis-generator-core-1.3.2\lib\mysql-connector-java-5.1.29.jar"/>
<context id="default" targetRuntime="MyBatis3">
<!-- optional,旨在創建class時,對注釋進行控制 -->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--jdbc的數據庫連接 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://192.168.127.134:3306/user_134?characterEncoding=utf8" userId="root"
password="123456" />
<!-- 非必需,類型處理器,在數據庫類型和java類型之間的轉換控制-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- Model模型生成器,用來生成含有主鍵key的類,記錄類 以及查詢Example類
targetPackage 指定生成的model生成所在的包名
targetProject 指定在該項目下所在的路徑
-->
<javaModelGenerator targetPackage="com.example.mytccdemo.db134.model" targetProject="./src/main/java">
<!-- 是否允許子包,即targetPackage.schemaName.tableName -->
<property name="enableSubPackages" value="false"/>
<!-- 是否對model添加 構造函數 -->
<property name="constructorBased" value="true"/>
<!-- 是否對類CHAR類型的列的數據進行trim操作 -->
<property name="trimStrings" value="true"/>
<!-- 建立的Model對象是否 不可改變 即生成的Model對象不會有 setter方法,只有構造方法 -->
<property name="immutable" value="false"/>
</javaModelGenerator>
<!--mapper映射文件生成所在的目錄 為每一個數據庫的表生成對應的SqlMap文件 -->
<sqlMapGenerator targetPackage="mappers/db134" targetProject="./src/main/resources">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mytccdemo.db134.dao" targetProject="./src/main/java">
<!-- enableSubPackages:是否讓schema作為包的后綴 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<table schema="user" tableName="account_a" domainObjectName="AccountA" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
</context>
</generatorConfiguration>
2) 生成后的結構

4、增加配置
ConfigDb134
@Configuration
@MapperScan(value = "com.example.mytccdemo.db134.dao", sqlSessionFactoryRef = "sqlSessionFactoryBean134")
public class ConfigDb134 {
@Bean("db134")
public DataSource db134(){
MysqlDataSource xaDs = new MysqlDataSource();
xaDs.setUser("root");
xaDs.setPassword("123456");
xaDs.setUrl("jdbc:mysql://192.168.127.134:3306/user_134");
return xaDs;
}
@Bean("sqlSessionFactoryBean134")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("db134") DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("mappers/db134/*.xml"));
return sqlSessionFactoryBean;
}
/**
* 事務管理器
* @return
*/
@Bean("tm134")
public PlatformTransactionManager transactionManager(@Qualifier("db134") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
ConfigDb129
@Configuration
@MapperScan(value = "com.example.mytccdemo.db129.dao", sqlSessionFactoryRef = "sqlSessionFactoryBean129")
public class ConfigDb129 {
@Bean("db129")
public DataSource db129(){
MysqlDataSource xaDs = new MysqlDataSource();
xaDs.setUser("root");
xaDs.setPassword("123456");
xaDs.setUrl("jdbc:mysql://192.168.127.129:3306/user_129");
return xaDs;
}
@Bean("sqlSessionFactoryBean129")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("db129") DataSource dataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("mappers/db129/*.xml"));
return sqlSessionFactoryBean;
}
/**
* 事務管理器
* @return
*/
@Bean("tm129")
public PlatformTransactionManager transactionManager(@Qualifier("db129") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
5、創建服務層
@Service
public class AccountService {
@Resource
private AccountAMapper accountAMapper;
@Resource
private AccountBMapper accountBMapper;
@Transactional(transactionManager = "tm134", rollbackFor = Exception.class)
public void transferAccount(){
//AccountA減去200
AccountA accountA = accountAMapper.selectByPrimaryKey(1);
accountA.setBalance(accountA.getBalance().subtract(new BigDecimal(200)));
accountAMapper.updateByPrimaryKey(accountA);
//AccountB增加200
AccountB accountB = accountBMapper.selectByPrimaryKey(2);
accountB.setBalance(accountB.getBalance().add(new BigDecimal(200)));
accountBMapper.updateByPrimaryKey(accountB);
try {
//模擬異常
int i = 1/0;
}catch (Exception e){
//AccountB增加200出現異常,進行補償操作,減去200
//補償如果發生錯誤,可以增加重試機制,比如重試3后,仍然失敗,則進行記錄。然后進行人工處理。處理復雜,所以不建議使用補償機制。
AccountB accountb = accountBMapper.selectByPrimaryKey(2);
accountb.setBalance(accountb.getBalance().subtract(new BigDecimal(200)));
accountBMapper.updateByPrimaryKey(accountb);
throw e;
}
}
}
6、單元測試
@SpringBootTest
class MyTccDemoApplicationTests {
@Autowired
private AccountService accountService;
@Test
void testAccount() {
accountService.transferAccount();;
}
}
總結: 事務補償達到了預期的效果。但是使用代碼編寫,實現比較復雜,不建議使用。
