项目中将一个库的某些标的某些数据保存到另一个库。
使用spring的aop编程动态切换数据源,代码如下,以备下次用到!
1.先将两个数据库连接,创建两个数据源,交于spring管理!
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> </bean>
2.将动态切换数据源的类交给spring
<bean id="dynamicDataSource" class="cn.**.**.common.db.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 指定lookupKey和与之对应的数据源 -->
<entry key="dataSource" value-ref="dataSource"></entry>
<entry key="dataSource2" value-ref="dataSource2"></entry>
</map>
</property>
<!-- 这里可以指定默认的数据源 -->
<property name="defaultTargetDataSource" ref="dataSource" />
</bean>
3.创建数据源切换的类,必须继承 AbstractRoutingDataSource 这个类(数据源动态切换必须继承该类)
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 从自定义的位置获取数据源标识 return DynamicDataSourceHolder.getDataSource(); } }
4.将数据源放进线程中,避免相互干扰
public class DynamicDataSourceHolder { /** * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰 */ private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() { String dataSource = THREAD_DATA_SOURCE.get(); if(StringUtils.isEmpty(dataSource)){ return DataSourceKey.DATA_SOURCE; }else{ return dataSource; } } public static void setDataSource(String dataSource) { THREAD_DATA_SOURCE.set(dataSource); } public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); } }
5.数据源的key
DataSourceKey 这个类是多个数据源在spring管理中的name标识,可通过key取得数据源。
6.设置默认的数据源。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { /** * 默认是某个数据源 */ String value() default DataSourceKey.DATA_SOURCE; }
7.根据spring的aop在执行之前将数据源放进线程中,动态切换数据源。
@Aspect @Component @Order(0) public class DataSourceAspect{ private Logger logger = Logger.getLogger(getClass()); @Pointcut("execution(public * cn.**.**.service..*Service.*(..))") public void dataSourceAspect(){}; /** * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源 */ @Before("dataSourceAspect()") public void before(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()); } 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()); }else{ DynamicDataSourceHolder.clearDataSource(); } // 方法注解可以覆盖类型注解 Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource source = m.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } logger.info("选择连接加载的DB :"+DynamicDataSourceHolder.getDataSource()); } catch (Exception e) { //如果出现异常不能断了所有连接,默认还是某个数据库连接 logger.error("选择连接加载DB ERROR "+clazz+":" +e.getMessage()); } } }
8.service层动态切换
默认的数据源不用标明,另一个数据源用@DataSource(DataSourceKey.LYD_DATA_SOURCE) 在service上标明即可。
9.另一个库的查询也可用@Results注解封装返回的实体(或map),sql用@Select注解
10.在多数据源切换中,不要在一个事务方法里面用到两个数据源,不然会切换不过去。因为事务一般定义在service层,所以如果用到两个数据源,可以将两个数据源放在controller进行。