Mybatis-plus创建动态数据源

3,050 阅读3分钟

1. 为什么需要动态数据源

当一个应用需要根据不同的情况,连接不同的数据库处理相同的业务逻辑时就需要使用动态数据源技术

2. 动态数据源和多数据源的区别

  • 多数据源:是属于静态的数据源切换,一个业务处理往往只能定义一个数据源,如果需要在不同的数据库上执行此处理,就需要些两个相同的数据来连接不同的数据源。
    多数据源的实现可以参照:官网文档
  • 动态数据源:是在多数源基础上的一次升级,数据源可以在同一个处理中来回切换,达到相同的业务代码可以运行在多个数据源上,这样就可以减少了冗余的开发量,而且将来的扩展性会变得更加强大。

3. 动态数据源的实现

3.1 POM中导入依赖的组件

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.1.tmp</version>
</dependency>

数据库配置文件大致如下:

spring:
  datasource: #数据库配置
    primary: #数据库1
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://
      username: root
      password: 3
      type: com.alibaba.druid.pool.DruidDataSource
    second: #数据库2
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://
      username: root
      password: 3
      type: com.alibaba.druid.pool.DruidDataSource
    third: #数据库3
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://
      username: root
      password: 3
      type: com.alibaba.druid.pool.DruidDataSource

MybatisPlus 配置大致如下(mybatis 的配置可以删除)

mybatis-plus:
  # 扫描 mapper.xml
  mapper-locations: classpath:mapper/*.xml #也可以不配置,在代码中设置
#  configuration:
#    map-underscore-to-camel-case: false

3.2 代码实现

  • 先新建 数据源的枚举
public enum DataSourceEnums {

    PRIMARY("primaryDataSource"),
    SECOND("secondDataSource"),
    THIRD("thirdDataSource");

    private String value;

    DataSourceEnums(String value){this.value=value;}

    public String getValue() {
        return value;
    }

}
  • 用来标记数据源的 注解(在哪里使用哪个数据源)
/**
 * @author zhaww
 * @date 2020/4/14
 * @Description .自定义 - 区分数据源的注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSource {
    DataSourceEnums value() default DataSourceEnums.PRIMARY;
}
  • 动态数据源管理器,继承 AbstractRoutingDataSource
/**
 * @author zhaww
 * @date 2020/4/10
 * @Description .动态数据源管理器
 */public class DataSourceContextHolder extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();

    /**
     * 重写这个方法,这里返回使用的数据源 key 值
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
//        log.info("动态切换数据源:" + DataSourceContextHolder.getDataSource());
        return contextHolder.get();
    }

    /**
     *  设置数据源
     * @param db
     */
    public static void setDataSource(String db){
        contextHolder.set(db);
    }

    /**
     * 取得当前数据源
     * @return
     */
    public static String getDataSource(){
        return contextHolder.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clear(){
        contextHolder.remove();
    }

}
  • mybatis-plus 的配置类
/**
 * @author zhaww
 * @date 2020/4/10
 * @Description .
 */
//@EnableTransactionManagement //开启事务
@Configuration
@MapperScan(value = {"com.zydd.admin.dao"}) //扫描Mapper 层的类
public class MybatisPlusConfig {

    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.second")
    public DataSource secondDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "thirdDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.third")
    public DataSource thirdDataSource() {
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "multipleTransactionManager")
    @Primary
    public DataSourceTransactionManager multipleTransactionManager(@Qualifier("multipleDataSource") DataSource dataSource) {
//        return new MyDataSourceTransactionManager(dataSource);
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * 动态数据源配置
     *
     * @return
     */
    @Bean(name = "multipleDataSource")
    @Primary
    public DataSource multipleDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
                                         @Qualifier("secondDataSource") DataSource secondDataSource,
                                         @Qualifier("thirdDataSource") DataSource thirdDataSource) {
        DataSourceContextHolder dynamicDataSource = new DataSourceContextHolder();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceEnums.PRIMARY.getValue(), primaryDataSource);
        targetDataSources.put(DataSourceEnums.SECOND.getValue(), secondDataSource);
        targetDataSources.put(DataSourceEnums.THIRD.getValue(), thirdDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(thirdDataSource); // 默认使用的数据源
        return dynamicDataSource;
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(multipleDataSource(primaryDataSource(), secondDataSource(), thirdDataSource()));
      //mybatis-plus yml 配置不生效,要在这里代码里配置
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        //是否使用转驼峰
        configuration.setMapUnderscoreToCamelCase(false);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);

        //添加分页功能
        Interceptor[] plugins = {paginationInterceptor()};
        sqlSessionFactory.setPlugins(plugins);

        //扫描 mapper 路径
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resource = resolver.getResources("classpath:mapper/**/*.xml");
        sqlSessionFactory.setMapperLocations(resource);
        return sqlSessionFactory.getObject();
    }

    /**
     * @Description : mybatis-plus分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        paginationInterceptor.setOverflow(true);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
//        paginationInterceptor.setLimit(30);
        return paginationInterceptor;
    }

}

注意:我们不但默认通过 Mapper 的路径来切换数据源,还通过 Service 方法层来切换数据源。
因为如果 service 有事务的话,进入service方法的时候,DataSourceTransactionManager 就设置好了默认数据源,就算通过Mapper层重新设置数据源,
DataSourceTransactionManager 的默认数据源还是没有变。
所以在 事务管理器 设置默认数据源之前,就切换数据源,实现动态事务+动态数据源。

4. 问题总结

1.配置文件里 mybatis-plus的配置不生效:因为我们在 SqlSessionFactory 里重新写了 MybatisConfiguration 。
2.启用事务的话,动态数据源不生效:因为 service 有事务的话,在进入service方法时,DataSourceTransactionManager 就设置好了默认数据源。