Druid多数据源配置 (Datasurce动态切换)

  1. 多数据源切换自定义注解
package com.gateway.admin.datasources.annotation;

import java.lang.annotation.*;
/**
 * 多数据源注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}

 
  1. 多数据源,切面处理类
package com.gateway.admin.datasources;

import com.gateway.admin.datasources.annotation.DataSource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 多数据源,切面处理类
 */
@Aspect
@Component
public class DataSourceAspect implements Ordered {
    protected Logger logger = LoggerFactory.getLogger(getClass());
    
    /**
	 * 针对上面注解做切面拦截
 	 */
    @Pointcut("@annotation(com.gateway.admin.datasources.annotation.DataSource)")
    public void dataSourcePointCut() {}

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource ds = method.getAnnotation(DataSource.class);
        if(ds == null){
        	//如果没有注解,使用默认数据源
            DynamicDataSource.setDataSource(DataSourceNames.FIRST);
        }else {
        	//根据注解中设置的数据源名称,选择对应的数据源
            DynamicDataSource.setDataSource(ds.name());
            logger.debug("set datasource is " + ds.name());
        }

        try {
            return point.proceed();
        } finally {
        	//清除数据源配置
            DynamicDataSource.clearDataSource();
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

 
  1. 动态数据源类,需要继承:AbstractRoutingDataSource
package com.gateway.admin.datasources;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;

/**
 * 动态数据源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

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

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

}


 

4 .数据源名称配置

package com.gateway.admin.datasources;

/**
 * 多数据源配置数据源
 */
public interface DataSourceNames {
    String FIRST = "first";
    String SECOND = "second";
    String THREE = "three";
    String FOUR = "four";
}

 

5 .多数据源配置类

package com.gateway.admin.datasources;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
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;

/**
 * 多数据源配置类
 */
@Configuration
public class DynamicDataSourceConfig {

	//如果ioc容器中,同一个类型有多个bean,则bean的名称为方法的名称
    @Bean
    @ConfigurationProperties("spring.datasource.druid.first")
    public DataSource firstDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.second")
    public DataSource secondDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.three")
    public DataSource threeDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.four")
    public DataSource fourDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource, DataSource threeDataSource, DataSource fourDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        targetDataSources.put(DataSourceNames.THREE, threeDataSource);
        targetDataSources.put(DataSourceNames.FOUR, fourDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }
}

 
  1. yml部分数据源相关配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        druid:
            first:  #db1
                url: jdbc:mysql://127.0.0.1:3306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
                username: root
                password: root
            second:  #db2
                url: jdbc:mysql://127.0.0.1:3306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
                username: root
                password: root
            three:  #db3
                url: jdbc:mysql://127.0.0.1:3306/db3?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
                username: root
                password: root
            four:   #db4
                url: jdbc:mysql://127.0.0.1:3306/db4?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
                username: root
                password: root

 
  1. 动态调用数据源示例代码
	//通过@DataSource以及name,动态指定对应的数据源
   	@DataSource(name = DataSourceNames.SECOND)
    public void test(String param) {
        testMapper.test(param);
    }

 

二. AbstractRoutingDataSource 补充

部分源码分析

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    @Nullable//目标数据源
    private Map<Object, Object> targetDataSources;

    @Nullable//默认数据源
    private Object defaultTargetDataSource;

    @Nullable//解析的数据源
    private Map<Object, DataSource> resolvedDataSources;

    @Nullable//解析默认的数据源
    private DataSource resolvedDefaultDataSource;

    @Override
    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        }
        this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
        this.targetDataSources.forEach((key, value) -> {
            Object lookupKey = resolveSpecifiedLookupKey(key);
            DataSource dataSource = resolveSpecifiedDataSource(value);
            this.resolvedDataSources.put(lookupKey, dataSource);
        });
        if (this.defaultTargetDataSource != null) {
        	//设置解析的默认数据源
            this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }
    }

	//数据源对应的key,默认和目标数据源map中的key一致
    protected Object resolveSpecifiedLookupKey(Object lookupKey) {
        return lookupKey;
    }

	 //将指定的数据源对象解析为 DataSource 实例
	 protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
	       if (dataSource instanceof DataSource) {
	           return (DataSource) dataSource;
	       }
	       else if (dataSource instanceof String) {
	           return this.dataSourceLookup.getDataSource((String) dataSource);
	       }
	       else {
	           throw new IllegalArgumentException(
	                   "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
	       }
	   }

//==========================================获取数据库连接=========================================

    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
    	//从特定的数据源中获取对应的数据库连接对象
        return determineTargetDataSource().getConnection(username, password);
    }

    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        //DynamicDataSource 中对 determineCurrentLookupKey()方法进行了重写
        Object lookupKey = determineCurrentLookupKey();
        //resolvedDataSources 对应的map中获取对应的数据源
        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;
    }

    @Nullable
    protected abstract Object determineCurrentLookupKey();

}

 

三. 多数据源 + ShardingJDBC 数据源整合

现有需求: 在多数据源能够动态切换的前提下,其中某个库中的表需要实现分表操作,例如:上述多数据源配置中的second 数据库中某个表需要实现分表操作,我们采用ShardingJDBC实现。此时second 数据源的配置需要更改为ShardingJDBC对应的数据源,如下面更改后的多数据源配置:

原来多数据源配置类

package com.gateway.admin.datasources;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
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;

/**
 * 多数据源配置类
 */
@Configuration
public class DynamicDataSourceConfig {

	//如果ioc容器中,同一个类型有多个bean,则bean的名称为方法的名称
    @Bean
    @ConfigurationProperties("spring.datasource.druid.first")
    public DataSource firstDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.second")
    public DataSource secondDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.three")
    public DataSource threeDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.four")
    public DataSource fourDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource, DataSource threeDataSource, DataSource fourDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        targetDataSources.put(DataSourceNames.THREE, threeDataSource);
        targetDataSources.put(DataSourceNames.FOUR, fourDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }
}

 

整合ShardingJDBC 后,多数据源配置类

package com.gateway.admin.datasources;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.shardingsphere.shardingjdbc.jdbc.adapter.AbstractDataSourceAdapter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 多数据源配置类
 */
@Configuration
public class DynamicDataSourceConfig {
   @Bean
    @ConfigurationProperties("spring.datasource.druid.first")
    public DataSource firstDataSource() {
        return DruidDataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties("spring.datasource.druid.three")
    public DataSource threeDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.four")
    public DataSource fourDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

//该数据源为shardingjdbc对应的数据源,可通过java代码配置,也可通过配置文件配置,此处我们采用yml配置,配置文件下面会给出
    /**
     * shardingjdbc有四种数据源,需要根据业务注入不同的数据源
     *
     * <p>1. 未使用分片, 脱敏的名称(默认): shardingDataSource;
     * <p>2. 主从数据源: masterSlaveDataSource;
     * <p>3. 脱敏数据源:encryptDataSource;
     * <p>4. 影子数据源:shadowDataSource
     *
     */
    @Lazy
    @Resource(name = "shardingDataSource")
    private AbstractDataSourceAdapter shardingDataSource;

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource threeDataSource, DataSource fourDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, shardingDataSource);
        targetDataSources.put(DataSourceNames.THREE, threeDataSource);
        targetDataSources.put(DataSourceNames.FOUR, fourDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }
}

 

对应的yml配置文件为(我们此处只是针对单个库做分表操作):

# Tomcat
server:
  tomcat:
    uri-encoding: UTF-8
    max-threads: 1000
    min-spare-threads: 30
  port: 8888

spring:
  #shardingjdbc--------------------------------------------------------------------------------------------------------
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    props:
      sql:
        show: true
    dataSource:
      names: ds0
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/gateway_stable?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: root
    sharding:
      tables:
        t_order:
          logicTable: t_order
          actualDataNodes: ds0.t_order$->{1..2}
          tableStrategy:
#            standard:
#              shardingColumn: id
#              preciseAlgorithmClassName: com.personal.datasources.MonthPreciseShardingAlgorithm
#              rangeAlgorithmClassName:
            inline:
              shardingColumn: id
              algorithmExpression: t_order$->{id % 2 + 1}
          keyGenerator:
            type: SNOWFLAKE
            column: id
            worker:
              id: 1

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    druid:
      first:  #数据源1
        url: jdbc:mysql://172.16.43.43:3306/wanshu_admin?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
#      second:  #数据源2
#        url: jdbc:mysql://127.0.0.1:3306/gateway_stable?useUnicode=true&characterEncoding=utf-8&useSSL=false
#        username: root
#        password: root
      three:  #数据源3
        url: jdbc:mysql://172.16.43.43:3306/datacenter_stable?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
      four:   #数据源4
        url: jdbc:mysql://172.16.43.43:3306/chuanglan_risk_control?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
      initial-size: 10
      max-active: 100
      min-idle: 10
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        login-username: admin
        login-password: admin
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: false
        wall:
          config:
            multi-statement-allow: true


mybatis:
  mapper-locations: classpath:mapper/**/*.xml

 

关于shardingJDBC的相关知识,可参考:shardingJDBC分库分表


版权声明:本文为weixin_43887285原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。