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 THREAD_DATA_SOURCE = new ThreadLocal();

    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"?>


    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">

    class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">

        
        
        

        
        

        
        
        
        
        
        
        
        

        
        
        
        

        
        
        
        
        
        

        
        

        
        
        
        

        
    

    class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">

        
        
        

        
        

        
        
        
        
        
        
        
        

        
        
        
        

        
        
        
        
        
        

        
        

        
        
        
        

        
    

    class="com.inspur.tax.common.utils.ThreadLocalRountingDataSource">
        
            
                
                
            
        
        
    

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

    @Override
    public Map getYyssqk(String qxswjguandm) {
        DataSourceTypeManager.setDataSource("slave");
        System.out.println(DataSourceTypeManager.getDataSource());
        Map params = new HashMap();
        params.put("qxswjguandm", qxswjguandm);
        Map qnMap = sskjjMapper.getYyssqk(params);
        params.remove("tb");
        Map 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 getYyssqk(String qxswjguandm) {
        System.out.println(DataSourceTypeManager.getDataSource());
        Map params = new HashMap();
        params.put("qxswjguandm", qxswjguandm);