使用Spring的AbstractRoutingDataSource实现多数据源动态切换

1、配置多个数据源

<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
     <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver">
     </property>
     <property name="url" value="jdbc:jtds:sqlserver://127.0.0.1;databaseName=test">
     </property>
     <property name="username" value="***"></property>
     <property name="password" value="***"></property>
 </bean>
 <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource">
     <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver">
     </property>
     <property name="url" value="jdbc:jtds:sqlserver://127.0.0.2:1433;databaseName=test">
     </property>
     <property name="username" value="***"></property>
     <property name="password" value="***"></property>
</bean>

2、定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换;由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。

package com.sgl.dataSource;
import
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDataSourceHolder();
    }

}

3、定义一个可以设置当前线程的变量的工具类,用于设置对应的数据源名称:

package com.sgl.dataSource;
/**
 * 可以设置当前线程的变量的工具类,用于设置对应的数据源名称
 * @author 尐蘇
 *
 */
public class DynamicDataSourceHolder {
    /**
     * 当前线程
     */
    private static ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>();
    /**
     * 设置数据源名称
     * @param dataSourceType
     */
    public static void setDataSourceHolder(String dataSourceName){
        dataSourceHolder.set(dataSourceName);
    }
    /**
     * 获取数据源名称
     * @return
     */
    public static String getDataSourceHolder(){
        return dataSourceHolder.get();
    }
    /**
     * 清除数据源名称
     */
    public static void clearDataSourceHolder(){
        dataSourceHolder.remove();
    }
}

4、spring多数据源配置

<!-- 编写spring 配置文件的配置多数源映射关系 -->
<bean class="com.sgl.dataSource.DynamicDataSource" id="dataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry value-ref="dataSource1" key="dataSource1" />
            <entry value-ref="dataSource2" key="dataSource2" />
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="dataSource1">
    </property>
</bean> 

5、如果没有数据库的事务管理,已经可以实现数据库的动态切换了。但是如果涉及到数据库的事务管理,需要在数据库事务开启切换数据库,否则数据库的切换只能在下次数据库操作时才生效。可以定义一个aop处理类在数据库事务开启之前切换数据库:

package com.sgl.dataSource;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
/**
 * aop处理类,在数据库事务开启之前切换数据库
 * @author 尐蘇
 *
 */
public class DataSourceAspect implements MethodBeforeAdvice,AfterReturningAdvice{

    @Override
    public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
        DynamicDataSourceHolder.clearDataSourceHolder();
    }

    @Override
    public void before(Method method, Object[] arg1, Object arg2) throws Throwable {
        
        if (method.isAnnotationPresent(DataSource.class)) {
            DataSource dataSource = method.getAnnotation(DataSource.class);
            DynamicDataSourceHolder.setDataSourceHolder(dataSource.value());
        }else{
            //设置成默认的数据源
            DynamicDataSourceHolder.setDataSourceHolder("dataSource");
        }
        
    }

}

或者:

package com.sgl.dataSource;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;

/**
 * 实现数据源切换的类
 * @author 尐蘇
 *
 */
public class DataSourceExchange {
    /**
     * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
     * @param joinPoint
     * @throws Exception
     */
    public void beforeDaoMethod(JoinPoint joinPoint)throws Exception{
        Class<?> target = joinPoint.getTarget().getClass();//获取目标方法所在的类
        Signature signature = joinPoint.getSignature();
        Class<?>[] interfaces = target.getInterfaces();//确定此对象所表示的类或接口实现的接口
        for (Class<?> clazz : interfaces) {
            
        }
    }
    /**
     * 提取目标对象方法注解和类注解中的数据源标识
     * @throws Exception
     */
    public void resetDataSource(Class<?> clazz,Method method)throws Exception{
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 默认使用类注解
        if (clazz.isAnnotationPresent(DataSource.class)) {
            DataSource dataSource = clazz.getAnnotation(DataSource.class);//如果存在@DataSource注解,则返回这个注解,否则返回 null
            DynamicDataSourceHolder.setDataSourceHolder(dataSource.value());//把数据源名字绑定到本地线程
        }
        // 方法注解可以覆盖类注解
        Method method2 = clazz.getMethod(method.getName(), parameterTypes);
        if (method2!=null&&method2.isAnnotationPresent(DataSource.class)) {
            DataSource dataSource = method2.getAnnotation(DataSource.class);
            DynamicDataSourceHolder.setDataSourceHolder(dataSource.value());
        }
    }
}

6、设置数据库事务切面和切换数据库切面执行的顺序

<bean id="DataSourceAspect" class="com.sgl.dataSource" />
<aop:config>  
    <aop:pointcut id="transactionPointCut" expression="execution(* com.sgl.service.*.*(..))" />  
<!--     <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="transactionPointCut" order="1"/>   -->
    <aop:advisor advice-ref="DataSourceAspect" pointcut-ref="transactionPointCut" order="1"/>  
    <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointCut" order="2" />  
</aop:config>

 

posted @ 2018-03-13 10:53  小蘇  阅读(287)  评论(0编辑  收藏  举报