Spring Boot:mybatis-plus + atomikos + druid 实现不同实例数据库的多数据源配置和分布式事务管理

想到工作上可能会用到多数据源,但是自己在这方面并不是很熟悉,于是在网上查阅了很多文章,结果发现,网上的文章要么版本太老有些过时,要么用的不是mybatis-plus而是mybaits,要么步骤繁琐、需要自己手动编写aop切面代码,要么在同一service层方法中只能使用@Transactional实现单个数据源的事务管理控制,总是觉得有点不太完美,所以综合了以上文章的不足之处和可以借鉴之处,在这里总结出了一个万能的、可以适应多方面需求的多数据源配置方法,以满足相对完整的、代码简练的分布式事务控制需求。现在分享给大家。

一. 从maven中导入必须用到的依赖

<dependencies>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.20version>
        dependency>
        

        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.3.1version>
        dependency>
        

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
            <version>1.1.22version>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jta-atomikosartifactId>
        dependency>
    dependencies>

以上除了一些必须的、通用的依赖之外,还引入了atomikos依赖包,这是实现分布式事务管理的关键。

二. application.yml文件的配置

这个配置是可以很灵活的,数据源配置都是自定义字段,到时候方便能够在java代码中引用,以下是两个数据源的示范:

server:
  port: 10010
spring:
  autoconfigure:
#停用druid连接池的自动配置
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
#选用druid的XADataSource数据源,因为这个数据源支持分布式事务管理
    type: com.alibaba.druid.pool.xa.DruidXADataSource
#以下是自定义字段
    dynamic:
      primary: master
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/local_test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&autoReconnect=true
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
          url: jdbc:mysql://localhost:3307/remote_test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&autoReconnect=true
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        validation-query: SELCET 1

因为我们把数据源相关的自动配置给关闭了,所以druid的自动配置项写在这里已经是无效的了,包括druid的监控功能在这里配置是不好使的。

三. Dao层配置

因为我们现在要使用的是多个数据源,所以在这些地方和单数据源配置相比,略微不同。
dao层结构如以下图:

Spring Boot:mybatis-plus + atomikos + druid 实现不同实例数据库的多数据源配置和分布式事务管理_第1张图片
因为这里模拟的两个数据源中这张表的结构都是相同的,所以实体类就共用同一个了。注意两个数据源表的实体类是可以放在同一个文件夹中的,但是每个数据源的mapper接口类和mapper.xml文件务必放在不同的文件夹中,比如本项目分别用了master和slave两个文件夹分别存放不同数据源的mapper接口和xml文件,这是实现数据源切换的关键。

贴出代码:

1.User

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user_test")
public class User {
    @TableId(type = IdType.AUTO)
    private Integer id;

    private String mobile;

    private String password;

    private String nikename;
}

2.MasterUserMapper

@Repository
public interface MasterUserMapper extends BaseMapper<User> {

    @Select("select * from user_test")
    List<User> listUser();

    @Select("select * from user_test")
    List<User> listPageUser(Page page);

    @Select("insert into user_test (mobile, password, nikename) values ('13777777777', 'abc123', '呦呦鹿鸣')")
    Integer addUser();

    Integer delUser();

}

3.SlaveUserMapper

@Repository
public interface SlaveUserMapper extends BaseMapper<User> {

    @Select("select * from user_test")
    List<User> listUser();

    @Select("select * from user_test")
    List<User> listPageUser(Page page);

    @Select("insert into user_test (mobile, password, nikename) values ('13777776666', 'abc123', '朗朗繁星')")
    Integer addUser();

    Integer delUser();

}

4.MasterUserMapper



<mapper namespace="cn.onesdream.dao.mapper.master.MasterUserMapper">
    <delete id="delUser" parameterType="java.lang.String">
        delete from user_test where nikename = "呦呦鹿鸣"
    delete>
mapper>

5.SlavaUserMapper



<mapper namespace="cn.onesdream.dao.mapper.slave.SlaveUserMapper">
    <delete id="delUser" parameterType="java.lang.String">
        delete from user_test where nikename = "朗朗繁星"
    delete>
mapper>

PS:在实际crud中不建议使用*通配符,这里使用只是为了示范测试省事。

四. 手动配置dataSource数据源类、sqlSessionFactory工厂类、sqlSessionTemplate模板类

这里需要建立两个手动配置类分别对应两个数据源,两个以上数据源以此类推

1.MasterDateSourceConfig

@Configuration
@MapperScan(basePackages = "cn.onesdream.dao.mapper.master", sqlSessionTemplateRef = "masterSqlSessionTemplate")
public class MasterDataSourceConfig {
    @Value("${spring.datasource.dynamic.datasource.master.url}")
    private String url;

    @Value("${spring.datasource.dynamic.datasource.master.username}")
    private String username;

    @Value("${spring.datasource.dynamic.datasource.master.password}")
    private String password;

    @Value("${spring.datasource.dynamic.datasource.master.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.type}")
    private String dataSourceClassName;

    @Value("${spring.datasource.dynamic.datasource.validation-query}")
    private String testQuery;
    
    @Bean(name = "masterDataSource")
    public DataSource masterDataSource() {
        AtomikosDataSourceBean sourceBean = new AtomikosDataSourceBean();
        sourceBean.setUniqueResourceName("masterDataSource");
        sourceBean.setXaDataSourceClassName(dataSourceClassName);
        sourceBean.setTestQuery("select 1");
        sourceBean.setBorrowConnectionTimeout(3);
        MysqlXADataSource dataSource = new MysqlXADataSource();
        dataSource.setUser(username);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        sourceBean.setXaDataSource(dataSource);
        return sourceBean;
    }

    @Bean(name = "masterSqlSessionFactory")
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/master/*Mapper.xml"));
        //手动设置session工厂时,需要手动添加分页插件
        Interceptor[] plugins = new Interceptor[1];
        plugins[0] = new PaginationInterceptor();
        sqlSessionFactoryBean.setPlugins(plugins);
        return sqlSessionFactoryBean.getObject();
    }

    @Bean(name = "masterSqlSessionTemplate")
    public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

2.SlaveDataSourceConfig

@Configuration
@MapperScan(basePackages = "cn.onesdream.dao.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate")
public class SlaveDataSourceConfig {
    @Value("${spring.datasource.dynamic.datasource.slave.url}")
    private String url;

    @Value("${spring.datasource.dynamic.datasource.slave.username}")
    private String username;

    @Value("${spring.datasource.dynamic.datasource.slave.password}")
    private String password;

    @Value("${spring.datasource.dynamic.datasource.slave.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.type}")
    private String dataSourceClassName;

    @Value("${spring.datasource.dynamic.datasource.validation-query}")
    private String testQuery;

    @Bean(name = "slaveDataSource")
    public DataSource slaveDataSource() {
        AtomikosDataSourceBean sourceBean = new AtomikosDataSourceBean();
        sourceBean.setUniqueResourceName("slaveDataSource");
        sourceBean.setXaDataSourceClassName(dataSourceClassName);
        sourceBean.setTestQuery("select 1");
        sourceBean.setBorrowConnectionTimeout(3);
        MysqlXADataSource dataSource = new MysqlXADataSource();
        dataSource.setUser(username);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        sourceBean.setXaDataSource(dataSource);
        return sourceBean;
    }

    @Bean(name = "slaveSqlSessionFactory")
    public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/slave/*Mapper.xml"));
        //手动设置session工厂时,需要手动添加分页插件
        Interceptor[] plugins = new Interceptor[1];
        plugins[0] = new PaginationInterceptor();
        sqlSessionFactoryBean.setPlugins(plugins);
        return sqlSessionFactoryBean.getObject();
    }
    @Bean(name = "slaveSqlSessionTemplate")
    public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

以上的配置有几个需要注意的点:

  1. 每个数据源对应一个配置类
  2. 每个配置类的@MapperScan注解不一样,各自对应自己mapper接口文件夹(这就是为什么要将不同数据源的mapper接口写在不同文件夹的原因了)
  3. 在配置sqlSession工厂类的时候,创建的是MybatisSqlSessionFactoryBean,是为了能够正常使用Mybatis-Plus组件的基本功能,比如通用的crud语句绑定。
  4. 配置工厂类的时候,需要指定各自mapper.xml存放的路径(这就是为什么要将不同数据源的mapper.xml写在不同文件夹的原因了)
  5. 配置工厂类的时候,需要手动将分页插件加进去。因为数据源相关的自动配置被我们关闭了,创建传统PaginationInterceptor类的方法已经不好使了。

五. 编写Controller层和Service层

到了这一步,意味着多数据源的配置已经全部完成了,接下来就可以测试并正式使用了。
以下测试代码可以参考下(我这里省略了Service层,只是测试下简单业务),整个完整demo项目包地址会在文末提供。

1. Test01Controller

@RestController
@RequestMapping("/manage/web/test01")
public class Test01Controller {

    @Autowired
    private MasterUserMapper masterUserMapper;

    @Autowired
    private SlaveUserMapper slaveUserMapper;

    @ApiOperation("多数据源基础查询测试")
    @GetMapping("/01")
    public ResponseResult test01(){
        List<User> users = null;
        List<User> users1 = null;
        users = masterUserMapper.listUser();
        users1 = slaveUserMapper.listUser();
        ArrayList<Object> list = new ArrayList<>();
        list.add(users);
        list.add(users1);
        return new ResponseResult(list);
    }

    @ApiOperation("测试mybatisPlus通用crud功能")
    @GetMapping("/02")
    public ResponseResult test02(){
        masterUserMapper.delUser();
        slaveUserMapper.delUser();
        User user = new User(null, "7758258", "谁知盘中餐,粒粒皆辛苦", "悯农");
        masterUserMapper.insert(user);
        slaveUserMapper.insert(user);
        return new ResponseResult();
    }

    @ApiOperation("分布式事务测试")
    @GetMapping("/03")
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult test03(){
        masterUserMapper.addUser();
        int i = 1/0;
        slaveUserMapper.addUser();
        return new ResponseResult();
    }

    @ApiOperation("mybatis-plus通用分页和自定义分页功能测试")
    @GetMapping("/04")
    public ResponseResult test04(Page page){
        Page page1 = masterUserMapper.selectMapsPage(page, null);
        List<User> pageUser1 = slaveUserMapper.listPageUser(page);
        Page page2 = slaveUserMapper.selectPage(page, null);
        List<User> pageUser2 = masterUserMapper.listPageUser(page);
        ArrayList<Object> list = new ArrayList<>();
        list.add(page1);
        list.add(pageUser1);
        list.add(page2);
        list.add(pageUser2);
        return new ResponseResult(list);
    }
}

以上用了swagger注解,方便生成开发文档。ResponseResult类是自定义的一个通用返回结果包装类。
完整demo项目地址:项目地址
如有纰漏,不吝指教。

你可能感兴趣的:(java,mysql,druid,分布式,数据库,spring,mysql,mybatis)