SpringBoot+Mybatis+atomikos realizes distributed transaction

Background: With the continuous development of business and the doubling of the amount of data, the performance of a single database creates a bottleneck. We may partition the database (that is, the sub-database and sub-table we often call). The partition mentioned here refers to the physical After partitioning, different libraries may be on different servers. At this time, the ACID of a single database can no longer adapt to this situation. In this ACID cluster environment, it is almost difficult to ensure the ACID of the cluster. Achieve, or even if it can achieve that efficiency and performance will be greatly reduced, this time we will use distributed transactions

What is Atomikos for? 

Atomikos is an open source transaction manager that provides value-added services for the Java platform. It is mainly used to handle cross-database transactions. For example, a certain instruction is written in library A and library B, and the business requires the writing of library A and library B. The operation must be atomic, and atomikos can be used at this time.

For specific implementation, please look down↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

1. Introduce jar packages related to distributed transactions

        <!-- 阿里 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.4</version>
        </dependency>
        <!--分布式事务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>

Two, application.yml property configuration

spring:
  datasource:
    pre :
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test1?useUnicode=true&amp;characterEncoding=utf8&amp;characterResultSets=utf8
      user: users
      password: 123456
    sit :
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test2?useUnicode=true&amp;characterEncoding=utf8&amp;characterResultSets=utf8
      user: userbyuer
      password: 123456

Three, configure the data source

 Create two Atomikos data sources

@Configuration
public class DataSourceConfig {

    // 将这个对象放入spring容器中(交给Spring管理)
    @Bean
    // 读取 application.yml 中的配置参数映射成为一个对象
    @ConfigurationProperties(prefix = "spring.datasource.pre")
    public XADataSource getDataSource1(){
        // 创建XA连接池
        return new MysqlXADataSource();
    }

    /**
     * 创建Atomikos数据源
     * 注解@DependsOn("druidXADataSourcePre"),在名为druidXADataSourcePre的bean实例化后加载当前bean
     * @param xaDataSource
     * @return
     */
    @Bean
    @DependsOn("getDataSource1")
    @Primary
    public DataSource dataSourcePre(@Qualifier("getDataSource1") XADataSource xaDataSource){
        //这里的AtomikosDataSourceBean使用的是spring提供的
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(xaDataSource);
        return atomikosDataSourceBean;
    }


    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.sit")
    public XADataSource getDataSource2(){
        // 创建XA连接池
        return new MysqlXADataSource();
    }

    @Bean
    @DependsOn("getDataSource2")
    public DataSource dataSourceSit(@Qualifier("getDataSource2") XADataSource xaDataSource){
        //这里的AtomikosDataSourceBean使用的是spring提供的
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(xaDataSource);
        return atomikosDataSourceBean;
    }
}
  • The sitSqlSessionTemplate configuration class of data source 1

/**
 * 数据源Config1
 */
@Configuration
@MapperScan(basePackages = {"com.xiateng.dao.userbuyer"}, sqlSessionTemplateRef = "sitSqlSessionTemplate")
public class MybatisSitConfig {

    @Autowired
    @Qualifier("dataSourceSit")
    private DataSource dataSource;

    /**
     * 创建 SqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean
    @Primary
    public SqlSessionFactory sitSqlSessionFactory() throws Exception{
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 设置mybatis的xml所在位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath*:com/xiateng/mapper/userbuyer/*.xml"));
        return bean.getObject();
    }

    /**
     * 通过 SqlSessionFactory 来创建 SqlSessionTemplate
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    @Primary
    public SqlSessionTemplate sitSqlSessionTemplate(@Qualifier("sitSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        // SqlSessionTemplate是线程安全的,可以被多个DAO所共享使用
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

Entity class related to data source 1:

public class TUserBuyer {
    private Long userId;
    private Integer bond;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Integer getBond() {
        return bond;
    }

    public void setBond(Integer bond) {
        this.bond = bond;
    }
}

Dao interface related to data source 1:

public interface TUserBuyerMapper {
    int updateByPrimaryKeySelective(TUserBuyer record);
}

The dao interface related to data source 1 implements xml:

<update id="updateByPrimaryKeySelective" parameterType="com.xiateng.entity.TUserBuyer">
    update t_user_buyer
    <set>
      <if test="bond != null">
        bond = #{bond,jdbcType=INTEGER},
      </if>
    </set>
    where user_id = #{userId,jdbcType=BIGINT}
  </update>
 
  • The sitSqlSessionTemplate configuration class of data source 2

/**
 * 数据源Config2
 */
@Configuration
@MapperScan(basePackages = {"com.xiateng.dao.user"}, sqlSessionTemplateRef = "preSqlSessionTemplate")
public class MybatisPreConfig {

    @Autowired
    // @Qualifier表示查找Spring容器中名字为 preDataSource 的对象
    @Qualifier("dataSourcePre")
    private DataSource dataSource;

    /**
     * 创建 SqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean
    @Primary
    public SqlSessionFactory preSqlSessionFactory() throws Exception{
        // 用来创建 SqlSessionFactory 等同于下面配置
//        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
//            <property name="dataSource" ref="dataSource" />
//            <property name="mapperLocations" value="classpath:mybatis-mapper/*.xml"/>
//        </bean>
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 设置mybatis的xml所在位置(扫描mybatis的相关xml文件,装配到容器中)
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath*:com/xiateng/mapper/user/*.xml"));
        return bean.getObject();
    }

    /**
     * 通过 SqlSessionFactory 来创建 SqlSessionTemplate
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    @Primary
    public SqlSessionTemplate preSqlSessionTemplate(@Qualifier("preSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        // SqlSessionTemplate是线程安全的,可以被多个DAO所共享使用
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

Entity classes related to data source 2:

package com.xiateng.entity;

public class TtUser {
    private Long id;
    private Long updatedBy;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    public Long getUpdatedBy() {
        return updatedBy;
    }

    public void setUpdatedBy(Long updatedBy) {
        this.updatedBy = updatedBy;
    }
}

 Data source 2 related dao interface:

public interface TtUserMapper {
    int updateByPrimaryKeySelective(TtUser record);
}

 Data source 2 related interface implementation xml:

<update id="updateByPrimaryKeySelective" parameterType="com.xiateng.entity.TtUser">
    update t_user
    <set>
      <if test="updatedBy != null">
        updated_by = #{updatedBy,jdbcType=BIGINT}
      </if>
    </set>
    where id = #{id,jdbcType=BIGINT}
  </update>

Four, service layer code writing

public interface TUserService {

    int transactionalTest() throws Exception;
}
@Service
public class TUserServiceImpl implements TUserService{
    @Autowired
    private TtUserMapper ttUserMapper;
    @Autowired
    private TUserBuyerMapper tUserBuyerMapper;
    /**
     * 实现多数据库操作
     * @return
     */
    @Transactional
    public int transactionalTest() throws Exception{
        // sit(数据源1)
        TUserBuyer tUserBuyer = new TUserBuyer();
        tUserBuyer.setUserId(0L);
        tUserBuyer.setBond(444);
        tUserBuyerMapper.updateByPrimaryKeySelective(tUserBuyer);
        // pre(数据源2)
        TtUser ttUser = new TtUser();
        ttUser.setId(1L);
        ttUser.setUpdatedBy(444L);
        ttUserMapper.updateByPrimaryKeySelective(ttUser);
        //模拟异常
        int a = 1/0;
        return 1;
    }
}

Five, Controller layer code

   @RequestMapping(value = "/tUserList1")
    @ResponseBody
    public Map<String, Object> tUserList1(){
        Map<String, Object> map = new HashMap<>();
        int result = 0;
        try {
            result = tUserService.transactionalTest();
        } catch (Exception e) {
            e.printStackTrace();
            map.put("result","系统异常!");
            return map;
        }
        map.put("result",result);
        return map;
    }

Test result: Should we simulate an exception, the database data has not changed, indicating that a rollback operation was performed when the exception occurred, and the expected effect was achieved!

Finally, I provide the github source address: https://github.com/xiatengGG/springboot-atomikos

Guess you like

Origin blog.csdn.net/qq_43037478/article/details/111592101