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

1 篇文章 0 订阅
1 篇文章 0 订阅

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

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

<dependencies>
<!-- 	mysql数据库依赖组件-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>
        
<!-- 	mybatis-plus依赖组件-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>
        
<!-- 	druid连接池依赖组件-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>

<!--    多数据源的分布式事务控制-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </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: SELECT 1

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

三. Dao层配置

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

在这里插入图片描述
因为这里模拟的两个数据源中这张表的结构都是相同的,所以实体类就共用同一个了。注意两个数据源表的实体类是可以放在同一个文件夹中的,但是每个数据源的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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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项目地址:项目地址
如有纰漏,不吝指教。

欢迎关注博客朗朗繁星的博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值