完全实现 Springboot2.x + druid1.1.x配置多数据源并实现自动切换

13 篇文章 0 订阅
7 篇文章 0 订阅

看到网上有不少实现,但是大多讲的不仔细,或实现的不优雅这里记录一下我的实现方式。

实现思路

  1. 利用springboot配置多个数据源
  2. 配置默认数据源,编写数据源切换类
  3. 创建切面实现自动切换

UML类图

如下是实现该功能的类图
multiDataSource

具体实现

多数据源的实现

  1. 导入依赖
<dependencies>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!--mime-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.55</version>
        </dependency>
        <!--支持热更新-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--        druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
        <!--使用log4j2-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
    </dependencies>

2.创建多数据源bean的配置类


/**
 * 多数据源bean的配置类
 * @author hy
 */
@Configuration
public class MultipleDataSourceConfig {

    @Bean("master")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource createMasterDataSource(){
        return new DruidDataSource();
    }

    @Bean("slave1")
    @ConfigurationProperties(prefix = "spring.datasource.slave1")
    public DataSource createSlave1DataSource(){
        return new DruidDataSource();
    }

    /**
     * 设置动态数据源,通过@Primary 来确定主DataSource
     * @return
     */
    @Bean
    @Primary
    public DataSource createDynamicDataSource(@Qualifier("master") DataSource master, @Qualifier("slave1") DataSource slave1){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //设置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(master);
        //配置多数据源
        Map<Object, Object> map = new HashMap<>();
        map.put("master",master);
        map.put("slave1",slave1);
        dynamicDataSource.setTargetDataSources(map);
        return  dynamicDataSource;
    }
}

这里是使用配置类注入bean
使用注解@Configuration和ConfigurationProperties,bean的属性值由配置文件的 spring.datasource.master和spring.datasource.slave1决定
bean的类型是Datasource,名字是由注解@Bean 指定
随后在方法createDynamicDataSource中使用 @Qualifier 使用指定名字的

  1. 重写determineCurrentLookupKey() 方法
public class DynamicDataSource extends AbstractRoutingDataSource {

    Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

    @Override
    protected Object determineCurrentLookupKey() {
        logger.info("------------------当前数据源 {}", DynamicDataSourceSwitcher.getDataSource());
        return DynamicDataSourceSwitcher.getDataSource();
    }
}

AbstractRoutingDataSource 是spring jdbc提供的操作读数据源的抽象类,重写determineCurrentLookupKey() 指定获得当前数据源

查看父类AbstractRoutingDataSource 的源码

protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
		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 + "]");
		}
		return dataSource;
	}

上面是determineTargetDataSource方法,可以看到若determineCurrentLookupKey返回空则使用默认数据源

  1. 实现操作数据源的类
	/**
 * 操作数据源
 * @author hy
 */
public class DynamicDataSourceSwitcher {

    static Logger logger = LoggerFactory.getLogger(DynamicDataSourceSwitcher.class);

    public static final String Mater = "master";
    public static final String Slave1 = "slave1";

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

    public static void setDataSource(String name){
        logger.info("-------- 设置数据源数据源为 :{} ", name);
        contextHolder.set(name);
    }

    public static String getDataSource(){
        return contextHolder.get();
    }

    public static void cleanDataSource(){
        contextHolder.remove();
    }

}

利用ThreadLocal 将数据源与当前线程绑定,并提供get set方法

5.配置application.yml文件

spring:
  profiles: multi
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    master:
      url: jdbc:mysql://xxx:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false
      username: xxx
      password: xxx
      type: com.alibaba.druid.pool.DruidDataSource
      name: master
      initialize: true
      # 监控统计拦截的filters 有stat,wall,log4j
      filters: stat
    slave1:
      url: jdbc:mysql://xxx:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false
      username: xxx
      password: xxx
      type: com.alibaba.druid.pool.DruidDataSource
      name: slave1
      initialize: true
      filters: stat

以上就已经实现了多数据源的配置,要实现自动切换还需要加入aop切面

加入aop切面

  1. 自定义数据源的注解
	/**
 * 自定义的数据源的注解
 * @author hy
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.METHOD
})
public @interface MyDataSource {
    String value() default "master";
}

  1. 创建切面
/**
 * 创建aop切面
 * @author hy
 */
@Aspect
@Component
@Order(1) //需要加入切面排序
public class DynamicDataSourceAspect {
    private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    /**
     * 切入点只对@Service注解的类上的@DataSource方法生效
     * @param myDataSource
     */
    @Pointcut(value="@within(org.springframework.stereotype.Service) && @annotation(myDataSource)" )
    public void dynamicDataSourcePointCut(MyDataSource myDataSource){}

    @Before(value = "dynamicDataSourcePointCut(myDataSource)")
    public void switchDataSource(MyDataSource myDataSource) {
        DynamicDataSourceSwitcher.setDataSource(myDataSource.value());
    }

    /**
     * 切点执行完后 切换成主数据库
     * @param myDataSource
     */
    @After(value="dynamicDataSourcePointCut(myDataSource)")
    public void after(MyDataSource myDataSource){
        DynamicDataSourceSwitcher.cleanDataSource();
    }
}

使用:

	@Service
public class WxUserService {

    @Resource
    WxUserMapper wxUserMapper;

    public WxUser getUserById(Integer id){
        return wxUserMapper.selectByPrimaryKey(id);
    }

    @MyDataSource(value = DynamicDataSourceSwitcher.Slave1)
    public WxUser getUserByIdWithSlave(Integer id) {
        return wxUserMapper.selectByPrimaryKey(id);
    }
}

效果如图:
在这里插入图片描述

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值