Guns——AOP切换多数据源(二)

1.配置单数据源

  • 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.配置多数据源

  • 首先需要配置至少两个以上的不同的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<String> 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做对比
  • 首先创建两个datasource的数据源
  • 接着对数据源进行初始话
  • 接着创建一个DynamicDataSource类,将两个数据源通过key/value的形式保存到map中,key是指数据源的名称,value实际的数据源实例
  • 最后将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<Object, Object> hashMap = new HashMap<>();
        hashMap.put(mutiDataSourceProperties.getDataSourceNames()[0], dataSourceGuns);
        hashMap.put(mutiDataSourceProperties.getDataSourceNames()[1], bizDataSource);
        dynamicDataSource.setTargetDataSources(hashMap);
        dynamicDataSource.setDefaultTargetDataSource(dataSourceGuns);
        return dynamicDataSource;
    }

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

  • 首先定义注解作为切点
  • 通过反射的机制获取@DataSource注解
  • 如果有此注解,则调用DataSourceContextHolder.setDataSourceType(datasource.name())方法切换数据源,name则是从注解的值中获取而来
  • 如果没有此注解则将数据源设置为第一个数据源
  • 最后调用实际业务逻辑继续执行
@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 = target.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.使用多数据源的注意事项

由于引入多数据源,所以让spring事务的aop要在多数据源切换aop的后面

  • 将事务的aop的优先级order调成2
/**
 * 多数据源配置<br/>
 * <p>
 * 注:由于引入多数据源,所以让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;
    }
}

5.实战

通过在需要切换数据源的方法上打上@DataSource注解即可,value需要填上相对应的datasource名称

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值