最近做的系统有很多个数据源,所以我研究了一下Mybatis多数据源的配置,Springboot 2.x.x.RELEASE 版本之后连接池只要你不配置默认就是HikariCP,被称为最快速的连接池。由于这个项目不需要使用到Druid的监控,所以我使用HikariCP数据库连接池,需要用到aop动态切换,话不多说,上代码了。

  1. pom.xml大概要使用的包,仅供参考,我也需要自动生成代码所以也会有MybatisPlus的包

springboot整合mybatisPlus动态数据源切换实现saas mybatisplus动态数据源配置_bc

  1. yml文件的配置:

springboot整合mybatisPlus动态数据源切换实现saas mybatisplus动态数据源配置_bc_02

springboot整合mybatisPlus动态数据源切换实现saas mybatisplus动态数据源配置_数据源_03

  1. 创建类MybatisPlusConfig,配置注入数据源
package com.szylt.projects.common.config.mybatis.plus;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.szylt.projects.common.enums.DBTypeEnum;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author huangrongfu
 * @date 2021/3/9 14:48
 * Utils: Intellij Idea
 * Description: 多数据源配置
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * 创建第一个数据源
     * @return dataSource
     */
    @Bean(name = "primary")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource dataSource0() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建第二个数据源
     * @return dataSource
     */
    @Bean(name = "db2")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource dataSource1() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 动态数据源配置
     * @return dataSource
     */
    @Bean
    @Primary
    public DataSource multipleDataSource(@Qualifier("primary") DataSource primary,
                                         @Qualifier("db2") DataSource db2) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> dataSources = new HashMap<>();
        dataSources.put(DBTypeEnum.PRIMARY.getValue(), primary);
        dataSources.put(DBTypeEnum.DB2.getValue(), db2);
        dynamicDataSource.setTargetDataSources(dataSources);
        dynamicDataSource.setDefaultTargetDataSource(primary);
        return dynamicDataSource;
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        // 导入mybatissqlsession配置
        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
        // 指明数据源
        sessionFactory.setDataSource(multipleDataSource(dataSource0(), dataSource1()));
         //指明实体扫描(多个package用逗号或者分号分隔)
        sessionFactory.setTypeAliasesPackage("com.szylt.projects.project.entity");
        // 导入mybatis配置
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sessionFactory.setConfiguration(configuration);
        return sessionFactory.getObject();
    }
}
  1. 创建动态数据源的获取类DynamicDataSource继承AbstractRoutingDataSource:
package com.szylt.projects.common.config.mybatis.plus;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author huangrongfu
 * @date 2021/3/9 14:53
 * Utils: Intellij Idea
 * Description: 扩展Spring的AbstractRoutingDataSource抽象类,实现动态数据源(他的作用就是动态切换数据源)
 * AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心,
 * 这里对该方法进行Override。【上下文DbContextHolder为一线程安全的ThreadLocal】
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 取得当前使用哪个数据源
     * @return dbTypeEnum
     */

    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbType();
    }
}
  1. 创建获取,设置数据源的类DbContextHolder,为了线程安全使用了ThreadLocal:
package com.szylt.projects.common.config.mybatis.plus;

import com.szylt.projects.common.enums.DBTypeEnum;

/**
 * @author huangrongfu
 * @date 2021/3/9 14:57
 * Utils: Intellij Idea
 * Description: 设置,获取,清空 当前线程内的数据源变量。
 */
public class DbContextHolder {

    private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal();

    /**
     * 设置数据源
     * @param dbTypeEnum 数据库类型
     */
    public static void setDbType(DBTypeEnum dbTypeEnum) {
        CONTEXT_HOLDER.set(dbTypeEnum.getValue());
    }

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

    /**
     * 清除上下文数据
     */
    public static void clearDbType() {
        CONTEXT_HOLDER.remove();
    }
}
  1. 创建DBTypeEnum枚举类:
package com.szylt.projects.common.enums;

/**
 * @author huangrongfu
 * @date 2021/3/9 14:59
 * Utils: Intellij Idea
 * Description: 设置数据源
 */
public enum  DBTypeEnum {

    /**
     * 主数据源
     * */
    PRIMARY("primary"),

    /**
     * 数据库2
     * */
    DB2("db2");

    private final String value;

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

    public String getValue() {
        return value;
    }
}
  1. 再添加一个aop切面类DataSourceSwitchAspect,这样就可以动态切换数据源了:
package com.szylt.projects.common.config.annotation.aspect;

import com.szylt.projects.common.config.annotation.DataSourceSwitch;
import com.szylt.projects.common.config.mybatis.plus.DbContextHolder;
import com.szylt.projects.common.enums.DBTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * @author huangrongfu
 * @date 2021/3/9 15:30 com/szylt/projects/project/service
 * Utils: Intellij Idea
 * Description: AOP方式动态切换数据源 (为了保证AOP在事务注解之前生效,Order的值越小,优先级越高)
 */

@Slf4j
@Component
@Aspect
@Order(-100)
public class DataSourceSwitchAspect {

    @Pointcut("execution(* com.szylt.projects.project.service..*.*(..))")
    private void dbAspect() {
    }

    @Before("dbAspect()")
    public void db(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        DataSourceSwitch dataSourceSwitch = methodSignature.getMethod().getAnnotation(DataSourceSwitch.class);
        if (Objects.isNull(dataSourceSwitch) || Objects.isNull(dataSourceSwitch.value())) {
            DbContextHolder.setDbType(DBTypeEnum.PRIMARY);
        } else {
            switch (dataSourceSwitch.value().getValue()) {
                case "primary":
                    DbContextHolder.setDbType(DBTypeEnum.PRIMARY);
                    break;
                case "db2":
                    DbContextHolder.setDbType(DBTypeEnum.DB2);
                    break;
                default:
                    DbContextHolder.setDbType(DBTypeEnum.PRIMARY);
            }
        }
    }
}
  1. 再添加一个可以供我们用注解形式的调用方式的接口:
package com.szylt.projects.common.config.annotation;

import com.szylt.projects.common.enums.DBTypeEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author huangrongfu
 * @date 2020/11/6 14:13
 * Utils: Intellij Idea
 * Description: 多数据源切换
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceSwitch {

    /**
     * 指定数据源,默认主数据源
     * */
    DBTypeEnum value() default DBTypeEnum.PRIMARY;
}

配置以上是可以启动正常的。

  • 启动报错出现【jdbcUrl is required with driverClassName】异常检查下最近的yml配置文件,如下正确配置示例:
# springboot2.0 配置多数据源
  datasource:
    primary:
      url: jdbc:sqlserver://xxx.xxx.x.xx:xxxx;DatabaseName=数据库名
      username: xx
      password: xxxxx
      driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    db2:
      url: jdbc:sqlserver://xxx.xxx.x.xx:xxxx;DatabaseName=数据库名
      username: xx
      password: xxxxx
      driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver

改为:

datasource:
    primary:
      jdbc-url: jdbc:sqlserver://xxx.xxx.x.xx:xxxx;DatabaseName=数据库名
      username: xx
      password: xxxxx
      driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    db2:
      jdbc-url: jdbc:sqlserver://xxx.xxx.x.xx:xxxx;DatabaseName=数据库名
      username: xx
      password: xxxxx
      driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver

spring.datasource.url 数据库的 JDBC URL
spring.datasource.jdbc-url 用来重写自定义连接池

  • 官方文档的解释是:
    因为连接池的实际类型没有被公开,所以在您的自定义数据源的元数据中没有生成密钥,而且在IDE中没有完成(因为DataSource接口没有暴露属性)。另外,如果您碰巧在类路径上有Hikari,那么这个基本设置就不起作用了,因为Hikari没有url属性(但是确实有一个jdbcUrl属性)。
  1. 接下来开始在service里边使用不同的数据源把,先给大家我的serviceImpl的示例把!这是使用第一个数据源,其实配置默认的数据源以后默认的那个数据源是可以不用写注解表名的,另外一个数据源需要写上注解:
package com.szylt.projects.project.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.szylt.projects.common.config.annotation.DataSourceSwitch;
import com.szylt.projects.common.enums.DBTypeEnum;
import com.szylt.projects.project.entity.YRecordcard;
import com.szylt.projects.project.mapper.YRecordcardMapper;
import com.szylt.projects.project.service.IYRecordcardService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * <p>
 *  题库记录服务实现类
 * </p>
 *
 * @author huangrongfu
 * @since 2021-03-09
 */
@Service
@AllArgsConstructor
public class YRecordcardServiceImpl extends ServiceImpl<YRecordcardMapper, YRecordcard> implements IYRecordcardService {


    @Override
    @DataSourceSwitch(DBTypeEnum.DB2)
    public List<YRecordcard> getAll(){
        return baseMapper.selectList(new QueryWrapper<YRecordcard>().eq("R_ID",51416));
    }
}
  1. 测试用例:
/**
 * @author huangrongfu
 * @date 2020/11/6 10:36
 * Utils: Intellij Idea
 * Description:
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class UserServiceImplTest {
 
   @Autowired
    private YRecordcardServiceImpl recordcardService;

    @Autowired
    private YUserServiceImpl userService;
 
    @Test
    public void getAll() {
         boolean userInfo = userService.getUserInfo("15717088069");
        System.out.println(userInfo);
        List<YRecordcard> recordcards = recordcardService.getAll();
        System.out.println(recordcards);
    }
 }
  1. 控制台结果,也能看到确实使用了HikariCP的数据库连接池:

springboot整合mybatisPlus动态数据源切换实现saas mybatisplus动态数据源配置_bc_04