1. 程式人生 > 實用技巧 >分散式事務解決方案2--TCC事務補償實踐

分散式事務解決方案2--TCC事務補償實踐

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();;
    }

}

  

總結: 事務補償達到了預期的效果。但是使用程式碼編寫,實現比較複雜,不建議使用。