爷的眼睛闪亮
insideDotNet En_summerGarden

可以看到AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey,这个lookupKey就是数据源标识。
因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源了。
第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:

复制代码
1 public class DynamicDataSource extends AbstractRoutingDataSource {
2 
3     @Override
4     protected Object determineCurrentLookupKey() {
5         // 从自定义的位置获取数据源标识
6         return DynamicDataSourceHolder.getDataSource();
7     }
8 
9 }
复制代码

第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识,代码如下:

复制代码
 1 public class DynamicDataSourceHolder {
 2     /**
 3      * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
 4      */
 5     private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();
 6 
 7     public static String getDataSource() {
 8         return THREAD_DATA_SOURCE.get();
 9     }
10 
11     public static void setDataSource(String dataSource) {
12         THREAD_DATA_SOURCE.set(dataSource);
13     }
14 
15     public static void clearDataSource() {
16         THREAD_DATA_SOURCE.remove();
17     }
18 
19 }
复制代码

第三步:配置多个数据源和第一步里创建的DynamicDataSource的bean,简化的配置如下:

复制代码
 1 <!--创建数据源1,连接数据库db1 -->
 2 <bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
 3     <property name="driverClassName" value="${db1.driver}" />
 4     <property name="url" value="${db1.url}" />
 5     <property name="username" value="${db1.username}" />
 6     <property name="password" value="${db1.password}" />
 7 </bean>
 8 <!--创建数据源2,连接数据库db2 -->
 9 <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
10     <property name="driverClassName" value="${db2.driver}" />
11     <property name="url" value="${db2.url}" />
12     <property name="username" value="${db2.username}" />
13     <property name="password" value="${db2.password}" />
14 </bean>
15 <!--创建数据源3,连接数据库db3 -->
16 <bean id="dataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
17     <property name="driverClassName" value="${db3.driver}" />
18     <property name="url" value="${db3.url}" />
19     <property name="username" value="${db3.username}" />
20     <property name="password" value="${db3.password}" />
21 </bean>
22 
23 <bean id="dynamicDataSource" class="com.test.context.datasource.DynamicDataSource">  
24     <property name="targetDataSources">  
25         <map key-type="java.lang.String">
26             <!-- 指定lookupKey和与之对应的数据源 -->
27             <entry key="dataSource1" value-ref="dataSource1"></entry>  
28             <entry key="dataSource2" value-ref="dataSource2"></entry>  
29             <entry key="dataSource3 " value-ref="dataSource3"></entry>  
30         </map>  
31     </property>  
32     <!-- 这里可以指定默认的数据源 最好不要指定,指定后有坑-->
33     <property name="defaultTargetDataSource" ref="dataSource1" />  
34 </bean>  
复制代码

到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource("dataSource2")即可切换到数据源2并对数据库db2进行操作了。

示例代码如下:

复制代码
 1 @Service
 2 public class DataServiceImpl implements DataService {
 3     @Autowired
 4     private DataMapper dataMapper;
 5 
 6     @Override
 7     public List<Map<String, Object>> getList1() {
 8         // 没有指定,则默认使用数据源1
 9         return dataMapper.getList1();
10     }
11 
12     @Override
13     public List<Map<String, Object>> getList2() {
14         // 指定切换到数据源2
15         DynamicDataSourceHolder.setDataSource("dataSource2");
16         return dataMapper.getList2();
17     }
18 
19     @Override
20     public List<Map<String, Object>> getList3() {
21         // 指定切换到数据源3
22         DynamicDataSourceHolder.setDataSource("dataSource3");
23         return dataMapper.getList3();
24     }
25 }
复制代码

--------------------------------------------------------------------------------------华丽的分割线--------------------------------------------------------------------------------------------------

但是问题来了,如果每次切换数据源时都调用DynamicDataSourceHolder.setDataSource("xxx")就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。能不能直接通过注解的方式指定需要访问的数据源呢,比如在dao层使用@DataSource("xxx")就指定访问数据源xxx?当然可以!前提是,再加一点额外的配置^_^。
首先,我们得定义一个名为DataSource的注解,代码如下:

1 @Target({ TYPE, METHOD })
2 @Retention(RUNTIME)
3 public @interface DataSource {
4     String value();
5 }

然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:

复制代码
 1 public class DataSourceAspect {
 2 
 3     /**
 4      * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
 5      * 
 6      * @param point
 7      * @throws Exception
 8      */
 9     public void intercept(JoinPoint point) throws Exception {
10         Class<?> target = point.getTarget().getClass();
11         MethodSignature signature = (MethodSignature) point.getSignature();
12         // 默认使用目标类型的注解,如果没有则使用其实现接口的注解
13         for (Class<?> clazz : target.getInterfaces()) {
14             resolveDataSource(clazz, signature.getMethod());
15         }
16         resolveDataSource(target, signature.getMethod());
17     }
18 
19     /**
20      * 提取目标对象方法注解和类型注解中的数据源标识
21      * 
22      * @param clazz
23      * @param method
24      */
25     private void resolveDataSource(Class<?> clazz, Method method) {
26         try {
27             Class<?>[] types = method.getParameterTypes();
28             // 默认使用类型注解
29             if (clazz.isAnnotationPresent(DataSource.class)) {
30                 DataSource source = clazz.getAnnotation(DataSource.class);
31                 DynamicDataSourceHolder.setDataSource(source.value());
32             }
33             // 方法注解可以覆盖类型注解
34             Method m = clazz.getMethod(method.getName(), types);
35             if (m != null && m.isAnnotationPresent(DataSource.class)) {
36                 DataSource source = m.getAnnotation(DataSource.class);
37                 DynamicDataSourceHolder.setDataSource(source.value());
38             }
39         } catch (Exception e) {
40             System.out.println(clazz + ":" + e.getMessage());
41         }
42     }
43 
44 }
复制代码

最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法:

复制代码
1 <bean id="dataSourceAspect" class="com.test.context.datasource.DataSourceAspect" />
2     <aop:config>
3         <aop:aspect ref="dataSourceAspect">
4             <!-- 拦截所有service方法 -->
5             <aop:pointcut id="dataSourcePointcut" expression="execution(* com.test.*.dao.*.*(..))"/>
6             <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
7         </aop:aspect>
8     </aop:config>
复制代码

OK,这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。

示例代码如下:

复制代码
 1 @Service
 2 // 默认DataServiceImpl下的所有方法均访问数据源1
 3 @DataSource("dataSource1")
 4 public class DataServiceImpl implements DataService {
 5     @Autowired
 6     private DataMapper dataMapper;
 7 
 8     @Override
 9     public List<Map<String, Object>> getList1() {
10         // 不指定,则默认使用数据源1
11         return dataMapper.getList1();
12     }
13 
14     @Override
15     // 覆盖类上指定的,使用数据源2
16     @DataSource("dataSource2")
17     public List<Map<String, Object>> getList2() {
18         return dataMapper.getList2();
19     }
20 
21     @Override
22     // 覆盖类上指定的,使用数据源3
23     @DataSource("dataSource3")
24     public List<Map<String, Object>> getList3() {
25         return dataMapper.getList3();
26     }
27 }
复制代码

提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。

posted on 2017-09-09 10:41  爷的眼睛闪亮  阅读(253)  评论(0编辑  收藏  举报