Fork me on GitHub

AbstractRoutingDataSource动态选择数据源

当我们项目变大后,有时候需要多个数据源,接下来我们讲一种能等动态切换数据源的例子。

盗一下图:

  • 单数据源的场景(一般的Web项目工程这样配置进行处理,就已经比较能够满足我们的业务需求)
  • 多数据源多SessionFactory这样的场景,估计作为刚刚开始想象想处理在使用框架的情况下处理业务,配置多个SessionFactory,然后在Dao层中对于特定的请求,通过特定的SessionFactory即可处理实现这样的业务需求
  • 使用AbstractRoutingDataSource 的实现类,进行灵活的切换,可以通过AOP或者手动编程设置当前的DataSource,不用修改我们编写的对于继承HibernateSupportDao的实现类的修改,这样的编写方式比较好

我们这次讲的是第三种,第二种也可以实现,相对来说也比较简单。

第一步:写AbstractRoutingDataSource的实现类

package com.inspur.tax.common.utils;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @ClassName ThreadLocalRountingDataSource
 * @Author caozx
 * @Description //TODO $
 * @Date $ $
 **/
public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // 获取数据源
        return DataSourceTypeManager.getDataSource();
    }
}

第二步:设置动态选择的Datasource,这里用到了ThreadLocal。

package com.inspur.tax.common.utils;

/**
 * @ClassName DataSourceTypeManager
 * @Author caozx
 * @Description //TODO $
 * @Date $ $
 **/
public class DataSourceTypeManager {
    /**
     * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
     */
    private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();

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

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

    public static void clear() {
        THREAD_DATA_SOURCE.remove();
    }
}

第三步:在Spring核心容器中配置配置数据源

<?xml version="1.0" encoding="UTF-8"?>
<!-- wbw 2016.8.22 -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/jdbc 
        http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd">

    <bean id="dataSource_mysql" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">

        <property name="url" value="${dataSource.mysql.url}" />
        <property name="username" value="${dataSource.mysql.username}" />
        <property name="password" value="${dataSource.mysql.password}" />

        <!-- 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j
            防御sql注入的filter:wall -->
        <property name="filters" value="stat" />

        <!-- 最大连接池数量(缺省值:8) -->
        <property name="maxActive" value="20" />
        <!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时(缺省值:0) -->
        <property name="initialSize" value="1" />
        <!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
        <property name="maxWait" value="60000" />
        <!-- 最小连接池数量 -->
        <property name="minIdle" value="1" />

        <!-- 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接
            2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 (缺省值:1分钟) -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <!-- 连接保持空闲而不被驱逐的最长时间(缺省值:30分钟) -->
        <property name="minEvictableIdleTimeMillis" value="300000" />

        <!-- 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。(缺省值:false) -->
        <property name="testWhileIdle" value="true" />
        <!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。(缺省值:true) -->
        <property name="testOnBorrow" value="false" />
        <!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能(缺省值:false) -->
        <property name="testOnReturn" value="false" />

        <!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 -->
        <property name="validationQuery" value="SELECT 1" />

        <!-- 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。(缺省值:fasle) -->
        <property name="poolPreparedStatements" value="false" />
        <!-- 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100(缺省值:-1) -->
        <property name="maxOpenPreparedStatements" value="-1" />

        <!-- 在上面的配置中,通常你需要配置url、username、password,maxActive这三项 -->
    </bean>

    <bean id="dataSource_oracle" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">

        <property name="url" value="${dataSource.oracle.url}" />
        <property name="username" value="${dataSource.oracle.username}" />
        <property name="password" value="${dataSource.oracle.password}" />

        <!-- 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j
            防御sql注入的filter:wall -->
        <property name="filters" value="stat" />

        <!-- 最大连接池数量(缺省值:8) -->
        <property name="maxActive" value="20" />
        <!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时(缺省值:0) -->
        <property name="initialSize" value="1" />
        <!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
        <property name="maxWait" value="60000" />
        <!-- 最小连接池数量 -->
        <property name="minIdle" value="1" />

        <!-- 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接
            2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 (缺省值:1分钟) -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <!-- 连接保持空闲而不被驱逐的最长时间(缺省值:30分钟) -->
        <property name="minEvictableIdleTimeMillis" value="300000" />

        <!-- 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。(缺省值:false) -->
        <property name="testWhileIdle" value="true" />
        <!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。(缺省值:true) -->
        <property name="testOnBorrow" value="false" />
        <!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能(缺省值:false) -->
        <property name="testOnReturn" value="false" />

        <!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 -->
        <property name="validationQuery" value="SELECT 1 FROM DUAL" />

        <!-- 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。(缺省值:fasle) -->
        <property name="poolPreparedStatements" value="true" />
        <!-- 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100(缺省值:-1) -->
        <property name="maxOpenPreparedStatements" value="100" />

        <!-- 在上面的配置中,通常你需要配置url、username、password,maxActive这三项 -->
    </bean>

    <bean id="dataSource" class="com.inspur.tax.common.utils.ThreadLocalRountingDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="master" value-ref="dataSource_oracle"/>
                <entry key="slave" value-ref="dataSource_mysql"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataSource_oracle"></property>
    </bean>
</beans>

第四步:直接在调用dao层之前使用:

    @Override
    public Map<String, String> getYyssqk(String qxswjguandm) {
        DataSourceTypeManager.setDataSource("slave");
        System.out.println(DataSourceTypeManager.getDataSource());
        Map<String,Object> params = new HashMap<String, Object>();
        params.put("qxswjguandm", qxswjguandm);
        Map<String, String> qnMap = sskjjMapper.getYyssqk(params);
        params.remove("tb");
        Map<String, String> bnMap = sskjjMapper.getYyssqk(params);
        DecimalFormat df = new DecimalFormat(",##0.00");
        if(Double.parseDouble(qnMap.get("LJ_YYSR"))==0){

注意代码中的红色部分,这就切换到了slave所对应的数据源。注意在代码最后进行清除,重新设置到默认的数据源。

 

 DataSourceTypeManager.clear()

 

进行到这里就实现了动态选择数据源,是不是很简单,有没有觉得有点问题呢?没错,每次选择都得执行代码DataSourceTypeManager.setDataSource("slave"),我们可以用aop的注解的方式哟,直接在方法上添加注解(对于方法体内来回切换的那种就老老实实手写吧)。

第五步:写注解

 

package com.inspur.tax.common.utils;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicSwitchDataSource {
    String value() default "";
}

 

第六步:写切面类,前置通知和后置通知

package com.inspur.tax.common.utils;


import java.lang.reflect.Method;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName DataSourceAspect
 * @Author caozx
 * @Description //TODO $
 * @Date $ $
 **/
@Aspect
public class DataSourceAspect {
    
    private final static Logger log = LoggerFactory.getLogger(DataSourceAspect.class);

    @Pointcut("@annotation(com.inspur.tax.common.utils.DynamicSwitchDataSource)")
    private void pointCut(){}

    @Before("pointCut()")
    public void beforeMethod(JoinPoint jp){
        try {
            //获取抽象方法(接口的或抽象类的方法)
            Method method = ((MethodSignature) jp.getSignature()).getMethod();

            //这个是接口方法的注解,没有所以为null
            //DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class);

            //获取当前类的对象
            Class<?> clazz = jp.getTarget().getClass();
            //获取实现类的方法
            method = clazz.getMethod(method.getName(), method.getParameterTypes());
            //获取方法上的注解
            DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class);
            if (annotationClass == null) {
                //获取类上面的注解
                annotationClass = jp.getTarget().getClass().getAnnotation(DynamicSwitchDataSource.class);
                if (annotationClass == null) return;
            }
            //获取注解上的数据源的值的信息
            String dataSourceKey = annotationClass.value();
            if (dataSourceKey != null) { //给当前的执行SQL的操作设置特殊的数据源的信息
                DataSourceTypeManager.setDataSource(dataSourceKey);
            }
            log.info("AOP动态切换数据源");
        }catch (Exception e){
            log.info(e.getMessage());
        }
    }
    
    @After("pointCut()")
    public void after(JoinPoint jp){
        DataSourceTypeManager.clear();
        log.info("后置通知");
    }
}

第七步:调用

    @Override
    @DynamicSwitchDataSource("master")
    public Map<String, String> getYyssqk(String qxswjguandm) {
        System.out.println(DataSourceTypeManager.getDataSource());
        Map<String,Object> params = new HashMap<String, Object>();
        params.put("qxswjguandm", qxswjguandm);

 

posted @ 2019-06-12 18:51  爱跑步的星仔  阅读(866)  评论(0编辑  收藏  举报