# SpringBoot Mybatis 动态数据源配置

SpringBoot Mybatis 动态数据源配置

  • 业务需求场景:数据库读写分离,配置多个数据库使用
  • 框架使用:SpringBoot+Mybatis
Maven 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.3.4.RELEASE</version>
</dependency>
<!-- Druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.23</version>
</dependency>
<!-- Mysql -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<!-- Mybatisd的依赖  -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
<!-- Aspect --->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
yml 配置文件
server:
  port: 8001

spring:
  application:
    name: oscoreserver
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      master:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSl=false
      slave:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/core?characterEncoding=utf-8&useSSl=false

# Mybatis配置让Ioc扫描xml
mybatis:
  #domain路径
  mapper-locations: classpath*:mapper/**/*.xml


关闭数据源自动配置(exclude = { DataSourceAutoConfiguration.class })
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@MapperScan("com.li.springbootproject.mapper")
public class SpringbootprojectApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootprojectApplication.class, args);
    }

}

如果不关闭数据源自动配置,会出现死循环依赖的问题如下图:

在这里插入图片描述

数据源配置类DataSourceConfig
  • 这里有个坑,public DruidDataSource masterDataSource()应该使用DruidDataSource如果使用DataSource无法拿到ym配置文件中的配置文件,你会看到默认的两个数据源是SpringBoot的默认数据源HikariDataSource
@Configuration
public class DataSourceConfig {

    /**
     * 主数据源
     * @return
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DruidDataSource masterDataSource() {
        return new DruidDataSource();
    }

    /**
     * 从数据源
     * @return
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DruidDataSource slaveDataSource() {
        return new DruidDataSource();

    }

    /**
     * 自定义动态数据源
     * @param masterDataSource
     * @param slaveDataSource
     * @return
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }

}
数据源枚举类
public enum DataSourceType {

    /**
     * 主库
     */
    MASTER,

    /**
     * 从库
     */
    SLAVE
}
自定义注解MyDataSource
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSource {

    /**
     * 切换数据源名称
     */
    DataSourceType value() default DataSourceType.SLAVE;
}
Aop拦截器

将带有注解的方法,进行切面拦截,拦截之后拿到注解的值,通过值去判断应该使用哪一个数据源,从而进行切换数据源,之后进行查询。

@Aspect
@Order(1)
@Component
public class DataSourceAspect {
    private Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 切点:注解定义的方法都会被拦截
     */
    @Pointcut("@annotation(com.li.springbootproject.config.datasource.MyDataSource)")
    public void dsPointCut() {

    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        MyDataSource dataSource = method.getAnnotation(MyDataSource.class);
        if (dataSource != null) {
            // 设置切换数据源
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return point.proceed();
        } finally {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

}
线程局部变量池子

使用线程局部变量存储当前数据源的节点信息,从里面得到数据源信息。

public class DynamicDataSourceContextHolder {

    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源变量
     * @param dataSourceType
     */
    public static void setDataSourceType(String dataSourceType){
        log.info("切换到{}数据源", dataSourceType);
        CONTEXT_HOLDER.set(dataSourceType);
    }

    /**
     * 获取数据源变量
     * @return
     */
    public static String getDataSourceType(){
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearDataSourceType(){
        CONTEXT_HOLDER.remove();
    }
}
数据源切换核心
public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
        super.afterPropertiesSet();
    }

    /**
     * 根据Key获取数据源的信息
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}
  • 源码
protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = this.determineCurrentLookupKey();
    // 根据  key 或的数据源
    DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
    }

    if (dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    } else {
        return dataSource;
    }
}
测试代码:加上注解就行
@Override
@MyDataSource(value = DataSourceType.SLAVE)
public BackResult testDatasource() {
    BackResult backResult = new BackResult();
    List<Map<String, String>> maps = userMapper.queryUsers();
    backResult.setData(maps);
    backResult.setSuccess(true);
    backResult.setMessage("OK");
    return backResult;
}

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值