欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-23 13:01:18
...

背景:随着业务的不断发展,数据量的倍增,单个数据库的性能产生瓶颈,我们可能会对数据库进行分区(也就是我们常说的分库分表),这里所说的分区指的是物理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的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

三、配置数据源

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

数据源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的配置类

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