最近项目需要去研究了一下spring多数据源的配置和使用,为了后期从多个数据源拉取数据定时进行数据分析和报表统计做准备。由于以前的项目都是单数据源的,没有遇到这种场景,所以也一直没有去了解过如何配置多数据源。
后来发现其实基于spring来配置和使用多数据源还是比较简单的,因为spring框架已经预留了这样的接口可以方便数据源的切换。
第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.DynamicDataSource(); } }第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识,代码如下:
public class DynamicDataSourceHolder { /** * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰 */ private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); /** * * 创 建 人:coates * 创建时间: * 方法描述: 获取数据源 * @return */ public static String DynamicDataSource() { return THREAD_DATA_SOURCE.get(); } public static void setDataSource(String dataSource){ THREAD_DATA_SOURCE.set(dataSource); } /** * * 创 建 人:coates * 创建时间: * 方法描述:删除当前数据源 */ public static void clearDataSource(){ THREAD_DATA_SOURCE.remove(); }
第三步:配置多个数据源和第一步里创建的DynamicDataSource的bean,简化的配置如下:
<context:property-placeholder location="classpath:config/*.properties" /> <!-- 数据库连接池 --> <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="driverClassName" value="${jdbc.driver}" /> </bean> <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="${jdbc1.url}" /> <property name="username" value="${jdbc1.username}" /> <property name="password" value="${jdbc1.password}" /> <property name="driverClassName" value="${jdbc.driver}" /> </bean> <bean id="dynamicDataSource" class="com.shenpinkj.core.tools.datasource.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 指定lookupKey和与之对应的数据源 --> <entry key="dataSource1" value-ref="dataSource1"></entry> <entry key="dataSource2" value-ref="dataSource2"></entry> </map> </property> <!-- 这里可以指定默认的数据源 --> <property name="defaultTargetDataSource" ref="dataSource1" /> </bean>
到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource("dataSource2")即可切换到数据源2并对数据库db2进行操作了。为了方便切换我们试试注解方式
首先,我们得定义一个名为DataSource的注解,代码如下:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value(); }
然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:
import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; public class DataSourceAspect { /** * * 创 建 人:coates * 创建时间:2017年11月2日 * 方法描述:拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源 * @param point * @throws Exception */ public void intercept(JoinPoint point) throws Exception { Class<?> target = point.getTarget().getClass(); MethodSignature signature = (MethodSignature) point.getSignature(); // 默认使用目标类型的注解,如果没有则使用其实现接口的注解 for (Class<?> clazz : target.getInterfaces()) { resolveDataSource(clazz, signature.getMethod()); } resolveDataSource(target, signature.getMethod()); } /** * * 创 建 人:coates * 创建时间:2017年11月2日 * 方法描述:提取目标对象方法注解和类型注解中的数据源标识 * @param clazz * @param method */ private void resolveDataSource(Class<?> clazz, Method method) { try { Class<?>[] types = method.getParameterTypes(); // 默认使用类型注解 if (clazz.isAnnotationPresent(DataSource.class)) { DataSource source = clazz.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } // 方法注解可以覆盖类型注解 Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource source = m.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } } catch (Exception e) { System.out.println(clazz + ":" + e.getMessage()); } } }最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法:
<bean id="dataSourceAspect" class="com.shenpinkj.core.tools.datasource.DataSourceAspect" /> <aop:config> <aop:aspect ref="dataSourceAspect"> <!-- 拦截所有service方法 --> <aop:pointcut id="dataSourcePointcut" expression="execution(* com.shenpinkj.core.store.service.*.*(..))"/> <aop:before pointcut-ref="dataSourcePointcut" method="intercept" /> </aop:aspect> </aop:config>
这样我们每次都可以通过
@Service @DataSource("dataSource1") public class LogsServiceIpml implements LogsService { @Override @DataSource("dataSource2") public BasePager getLogs(Integer page, Integer rows, String column, Integer order) {}