SpringBoot+Mybatis+atomikos实现分布式事务

背景:随着业务的不断发展,数据量的倍增,单个数据库的性能产生瓶颈,我们可能会对数据库进行分区(也就是我们常说的分库分表),这里所说的分区指的是物理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的ACID已经不能适应这种情况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即使能达到那么效率和性能会大幅下降,这个时候我们就会用到分布式事务

Atomikos 是干嘛用的? 

Atomikos 是一个为Java平台提供增值服务的并且开源类事务管理器,主要用于处理跨数据库事务,比如某个指令在A库和B库都有写操作,业务上要求A库和B库的写操作要具有原子性,这时候就可以用到atomikos。

具体实现请往下看↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

一、引入分布式事务相关jar包

        <!-- 阿里 连接池 -->
        <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>

二、application.yml属性配置

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

三、配置数据源

 创建两个Atomikos数据源

@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;
    }
}
  • 数据源1的sitSqlSessionTemplate配置类

/**
 * 数据源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);
    }
}

数据源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;
    }
}

数据源1相关的dao接口:

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

数据源1相关的dao接口实现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>
 
  • 数据源2的sitSqlSessionTemplate配置类

/**
 * 数据源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);
    }
}

数据源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;
    }
}

 数据源2相关的dao接口:

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

 数据源2相关的接口实现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>

四、service层代码编写

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

五、Controller层代码

   @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;
    }

测试结果:应我们模拟了一个异常,数据库数据没变,说明发生异常的时候做了回滚操作,达到预期效果!

最后奉上github源码地址:https://github.com/xiatengGG/springboot-atomikos

猜你喜欢

转载自blog.csdn.net/qq_43037478/article/details/111592101