zoukankan      html  css  js  c++  java
  • spring 配置多数据源 (可行)

    可以看到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注解来指定数据源,则优先以方法上指定的为准。

  • 相关阅读:
    开发中常用js记录(二)
    c# 我所理解的 值类型 and 引用类型
    c# 枚举
    ModelState.IsValid总为false原因
    学习总结 之 WebApi服务监控 log4net记录监控日志
    How to Deinstall Oracle Clusterware Home Manually
    oracle client 低于 oracle server 端,导致报错ORA-01882
    转 zabbix 用户建立和中文化
    转 rman 恢复报错
    10g 升级到11g 失效对象2则
  • 原文地址:https://www.cnblogs.com/austinspark-jessylu/p/7497391.html
Copyright © 2011-2022 走看看