• 分布式事务解决方案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:UsersThinkDesktopMyBatismybatis-generator-core-1.3.2mybatis-generator-core-1.3.2libmysql-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();;
        }
    
    }
    

      

    总结: 事务补偿达到了预期的效果。但是使用代码编写,实现比较复杂,不建议使用。

  • 相关阅读:
    【(高职专科组)第十一届蓝桥杯省模拟赛答案】给定一个数列,请问找出元素之间最大的元素距离。
    【(高职专科组)第十一届蓝桥杯省模拟赛答案】给定一个数列,请问数列中最长的递增序列有多长。
    POJ 2391 二分+最大流
    HDU 4529 状压dp
    NYOJ 747贪心+dp
    NYOJ 745 dp
    HDU 2686 / NYOJ 61 DP
    HDU 4313树形DP
    HDU 4303 树形DP
    POJ 2342 树形DP
  • 原文地址:https://www.cnblogs.com/linlf03/p/14006736.html
Copyright © 2020-2023  润新知