Spring Boot 多数据源研究

请参考两个代码:

  • 一个是guns框架中的多数据源,https://blog.csdn.net/m0_37834471/article/details/84036484
  • 一个是github上多数据源代码,https://github.com/helloworlde/SpringBoot-DynamicDataSource

下面是对guns框架中单数据源和多数据源的解析。对于多数据源的使用,guns与上面github中的代码类似,只是多增加了注解@DataSource("xxx")来显式说明哪一个数据源

1.Guns配置单数据源

spring boot中首先配置好数据库连接信息

spring:
  profiles: local
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/guns?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT
    username: root
    password: root
    filters: wall,mergeStat

构造了一个druid属性类,用来填充datasource所需要的数据信息 ,并且此属性可以从application.properties文件中自动装配

public class DruidProperties {
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    private Integer initialSize = 2;
    private Integer minIdle = 1;
    private Integer maxActive = 20;
    private Integer maxWait = 60000;
    private Integer timeBetweenEvictionRunsMillis = 60000;
    private Integer minEvictableIdleTimeMillis = 300000;
    private String validationQuery = "SELECT 'x'";
    private Boolean testWhileIdle = true;
    private Boolean testOnBorrow = false;
    private Boolean testOnReturn = false;
    private Boolean poolPreparedStatements = true;
    private Integer maxPoolPreparedStatementPerConnectionSize = 20;
    private String filters = "stat";
 
    public void config(DruidDataSource dataSource) {
        dataSource.setUrl(this.url);
        dataSource.setUsername(this.username);
        dataSource.setPassword(this.password);
        dataSource.setDriverClassName(this.driverClassName);
        dataSource.setInitialSize(this.initialSize);
        dataSource.setMinIdle(this.minIdle);
        dataSource.setMaxActive(this.maxActive);
        dataSource.setMaxWait((long)this.maxWait);
        dataSource.setTimeBetweenEvictionRunsMillis((long)this.timeBetweenEvictionRunsMillis);
        dataSource.setMinEvictableIdleTimeMillis((long)this.minEvictableIdleTimeMillis);
        dataSource.setValidationQuery(this.validationQuery);
        dataSource.setTestWhileIdle(this.testWhileIdle);
        dataSource.setTestOnBorrow(this.testOnBorrow);
        dataSource.setTestOnReturn(this.testOnReturn);
        dataSource.setPoolPreparedStatements(this.poolPreparedStatements);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(this.maxPoolPreparedStatementPerConnectionSize);
 
        try {
            dataSource.setFilters(this.filters);
        } catch (SQLException var3) {
            var3.printStackTrace();
   } 
}

 主要是配置datasource的bean,另外一个DruidProperties实质上是为了构造datasource而需要的

    /**
     * druid配置
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidProperties druidProperties() {
        return new DruidProperties();
    }
 
    /**
     * 单数据源连接池配置
     */
    @Bean
    public DruidDataSource dataSource(DruidProperties druidProperties) {
        DruidDataSource dataSource = new DruidDataSource();
        druidProperties.config(dataSource);
        return dataSource;
    }

2.Guns配置多数据源

首先需要配置至少两个以上的不同的datasource的构造方法(下文配置了两个datasource,一个叫做datasource,另一个叫做bizDataSource)

/**
 * guns的数据源
 */
private DruidDataSource dataSource(DruidProperties druidProperties) {
   DruidDataSource dataSource = new DruidDataSource();
   druidProperties.config(dataSource);
   return dataSource;
}

/**
 * 多数据源,第二个数据源
 */
private DruidDataSource bizDataSource(DruidProperties druidProperties,     
      MutiDataSourceProperties mutiDataSourceProperties) {

   DruidDataSource dataSource = new DruidDataSource();
   druidProperties.config(dataSource);
   mutiDataSourceProperties.config(dataSource);
   return dataSource;
}

开始定义一个动态数据源的类 ,此类需要继承AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,方法返回的是一个数据源的字符串名称

public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource() {
    }
 
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

返回对应数据源的字符串名称是使用了 ThreadLocal方式进行线程副本的维护

public class DataSourceContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal();
 
    public DataSourceContextHolder() {
    }
 
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }
 
    public static String getDataSourceType() {
        return (String)contextHolder.get();
    }
 
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

接着需要配置真正的动态数据源DynamicDataSource的Bean,这里可以拿它跟刚刚上文的单一数据源DruidDataSource做对比

  1. 首先创建两个datasource的数据源
  2. 接着对数据源进行初始话
  3. 接着创建一个DynamicDataSource类,将两个数据源通过key/value的形式保存到map中,key是指数据源的名称,value实际的数据源实例
  4. 最后将map设置到DynamicDataSource类中,并且将其中一个datasource当成默认的数据源

   

/**
 * 多数据源连接池配置
 */
@Bean
public DynamicDataSource mutiDataSource(DruidProperties druidProperties, MutiDataSourceProperties mutiDataSourceProperties) {
 
        DruidDataSource dataSourceGuns = dataSource(druidProperties);
        DruidDataSource bizDataSource = bizDataSource(druidProperties, mutiDataSourceProperties);
 
        try {
            dataSourceGuns.init();
            bizDataSource.init();
        } catch (SQLException sql) {
            sql.printStackTrace();
        }
 
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        HashMap hashMap = new HashMap<>();
        hashMap.put(mutiDataSourceProperties.getDataSourceNames()[0], dataSourceGuns);
        hashMap.put(mutiDataSourceProperties.getDataSourceNames()[1], bizDataSource);
        dynamicDataSource.setTargetDataSources(hashMap);
        dynamicDataSource.setDefaultTargetDataSource(dataSourceGuns);
        return dynamicDataSource;
}

3.Guns利用aop技术切换数据源

  1. 首先定义注解作为切点,通过反射的机制获取@DataSource注解,
  2. 如果有此注解,则调用DataSourceContextHolder.setDataSourceType(datasource.name())方法切换数据源,name则是从注解的值中获取而来;如果没有此注解则将数据源设置为第一个数据源
  3. 最后调用实际业务逻辑继续执行
@Pointcut("@annotation(cn.stylefeng.roses.core.mutidatasource.annotion.DataSource)")
private void cut() { }
 
@Around("cut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = null;
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        } else {
            methodSignature = (MethodSignature)signature;
            Object target = point.getTarget();
            Method currentMethod = arget.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
            DataSource datasource = (DataSource)currentMethod.getAnnotation(DataSource.class);
            if (datasource != null) {
                DataSourceContextHolder.setDataSourceType(datasource.name());
                this.log.debug("设置数据源为:" + datasource.name());
            } else {
                DataSourceContextHolder.setDataSourceType(this.mutiDataSourceProperties.getDataSourceNames()[0]);
                this.log.debug("设置数据源为:dataSourceCurrent");
            }
 
            Object var7;
            try {
                var7 = point.proceed();
            } finally {
                this.log.debug("清空数据源信息!");
                DataSourceContextHolder.clearDataSourceType();
            }
 
            return var7;
        }
}

4.使用Guns多数据源的注意事项

  • 由于引入多数据源,所以让spring事务的aop要在多数据源切换aop的后面将事务的aop的优先级order调成2
  • 通过在需要切换数据源的方法上打上@DataSource注解即可,value需要填上相对应的datasource名称
/**
 * 多数据源配置
 *

 * 注:由于引入多数据源,所以让spring事务的aop要在多数据源切换aop的后面  *  * @author stylefeng  * @Date 2017/5/20 21:58  */ @Configuration @ConditionalOnProperty(prefix = "guns.muti-datasource", name = "open", havingValue = "true") @EnableTransactionManagement(order = 2, proxyTargetClass = true) @MapperScan(basePackages = {"cn.stylefeng.guns.modular.*.dao", "cn.stylefeng.guns.multi.mapper"}) public class MultiDataSourceConfig { 将多数据源的aop的优先级调成1,则此优先级必定大于事务的优先级,说明会先切换数据源再进行事务的执行 @Aspect public class MultiSourceExAop implements Ordered {       ...       public int getOrder() {         return 1;     } }

 

你可能感兴趣的:(Spring Boot 多数据源研究)