使用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>