zoukankan      html  css  js  c++  java
  • 【原】通过AOP实现MyBatis多数据源的动态切换

    【环境参数】
    1、开发框架:Spring + SpringMVC + MyBatis

    2、数据库A的URL:jdbc.url=jdbc:mysql://172.16.17.164:3306/ test?characterEncoding=UTF-8&useUnicode=TRUE&autoReconnect=true&failOverReadOnly=false

    3、数据库B的URL:bakdb.jdbc.url=jdbc:mysql://172.16.17.68:3306/bakDB?characterEncoding=UTF-8&useUnicode=TRUE&autoReconnect=true&failOverReadOnly=false


    【需求描述】
    (1)当用户调用X方法“之前”,系统会首先切换当前数据源为A数据源(bakDb数据库),之后再去调用方法X。
    (2)当用户调用Y方法“之前”,系统会首先切换当前的数据源为B数据源(testDb数据库),之后再去调用方法Y。

    (3)X方法和Y方法所在的包名

        X方法:该方法位于com.zjrodger.bakdata.service包下其子包下。

        Y方法:该方法位于com.zjrodger.datatobank.service或者com.zjrodger.zxtobank.service包及其子包下。

    【具体步骤】
    1、编写动态数据源相关代码
    (1) 编写DynamicDataSource类。
    DynamicDataSource的主要作用是以Map<Object, Object>的形式,来存储多个数据源。
    因为该类继承了父类AbstractRoutingDataSource,在父类中,多数据源的实例是被存放在一个名为“targetDataSource”的Map类型的成员变量中。

    1 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    2 
    3 public class DynamicDataSource extends AbstractRoutingDataSource {
    4 
    5     @Override
    6     protected Object determineCurrentLookupKey() {
    7         return DatabaseContextHolder.getDbType();
    8     }
    9 }
    DynamicDataSource

    (2) 编写DatabaseContextHolder类。

     1 public class DatabaseContextHolder {
     2 
     3     private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
     4 
     5     public static void setDbType(String dataSourceType) {
     6         contextHolder.set(dataSourceType);
     7     }
     8 
     9     public static String getDbType() {
    10         return contextHolder.get();
    11     }
    12 
    13     public static void clearDbType() {
    14         contextHolder.remove();
    15     }
    16 }
    DatabaseContextHolder

    2、编写切换数据源的拦截器。

     1 public class DataSourceInterceptor {
     2     
     3     /** 数据源切换常量 */
     4     public static final String DATASOURCE_TEST_DB="dataSourceKey4TestDb";
     5     public static final String DATASOURCE_BAK_DB="dataSourceKey4BakDb";
     6 
     7     /**
     8      * 设置数据源为test数据库所对应的数据源。
     9      * @param jp
    10      */
    11     public void setdataSourceTestDb(JoinPoint jp) {
    12         DatabaseContextHolder.setDbType(DATASOURCE_TEST_DB);
    13     }
    14     
    15     /**
    16      * 设置数据源为bak数据库所对应的数据源。
    17      * @param jp
    18      */
    19     public void setdataSourceBakDb(JoinPoint jp) {
    20         DatabaseContextHolder.setDbType(DATASOURCE_BAK_DB);
    21     }
    22 }
    DataSourceInterceptor

    3、在Spring配置文件中进行相关配置。
    (1)配置两个数据源
    A.第一个数据源:

     1 <bean id="c3p0DataSource4BakDb" class="com.mchange.v2.c3p0.ComboPooledDataSource"
     2     destroy-method="close" depends-on="propertyConfigurer">
     3     <property name="driverClass" value="${bakdb.jdbc.driverclass}" />
     4     <property name="jdbcUrl" value="${bakdb.jdbc.url}" />
     5     <property name="user" value="${bakdb.jdbc.username}" />
     6     <property name="password" value="${bakdb.jdbc.password}" />
     7 
     8     <!-- 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
     9     <property name="initialPoolSize" value="10" />
    10     <!-- 连接池中保留的最小连接数。 -->
    11     <property name="minPoolSize" value="5" />
    12     <!-- 连接池中保留的最大连接数。Default: 15 -->
    13     <property name="maxPoolSize" value="100" />
    14     <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
    15     <property name="acquireIncrement" value="5" />
    16     <!-- 最大空闲时间,10秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
    17     <property name="maxIdleTime" value="10" />
    18     <!-- JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 
    19         如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
    20     <property name="maxStatements" value="0" />
    21     <!-- 连接池用完时客户调用getConnection()后等待获取连接的时间,单位:毫秒。超时后会抛出 SQLEXCEPTION,如果设置0,则无限等待。Default:0 -->
    22     <property name="checkoutTimeout" value="30000" />
    23 </bean>
    BakDb数据库的数据源

    B.第二个数据源:

     1 <bean id="c3p0DataSource4TestDb" class="com.mchange.v2.c3p0.ComboPooledDataSource"
     2     destroy-method="close" depends-on="propertyConfigurer">
     3     <property name="driverClass" value="${jdbc.driverclass}" />
     4     <property name="jdbcUrl" value="${jdbc.url}" />
     5     <property name="user" value="${jdbc.username}" />
     6     <property name="password" value="${jdbc.password}" />
     7 
     8     <!-- 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
     9     <property name="initialPoolSize" value="10" />
    10     <!-- 连接池中保留的最小连接数。 -->
    11     <property name="minPoolSize" value="5" />
    12     <!-- 连接池中保留的最大连接数。Default: 15 -->
    13     <property name="maxPoolSize" value="100" />
    14     <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
    15     <property name="acquireIncrement" value="5" />
    16     <!-- 最大空闲时间,10秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
    17     <property name="maxIdleTime" value="10" />
    18     <!-- JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 
    19         如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
    20     <property name="maxStatements" value="0" />
    21     <!-- 连接池用完时客户调用getConnection()后等待获取连接的时间,单位:毫秒。超时后会抛出 SQLEXCEPTION,如果设置0,则无限等待。Default:0 -->
    22     <property name="checkoutTimeout" value="30000" />
    23 </bean>
    TestDb数据库的数据源

    (2)两个数据源所对应的properties属性文件

     1 # =========== Test数据库相关信息 ============
     2 jdbc.url=jdbc:mysql://172.16.17.164:3306/ test?characterEncoding=UTF-8&amp;useUnicode=TRUE&amp;autoReconnect=true&amp;failOverReadOnly=false
     3 jdbc.username=root
     4 jdbc.password=123456
     5 jdbc.driverclass=com.mysql.jdbc.Driver
     6 jdbc.ip=172.16.5.64
     7 jdbc.dbname=test
     8 
     9 
    10 # =========== BakDB数据库相关信息 ============
    11 bakdb.jdbc.url=jdbc:mysql://172.16.17.68:3306/bakDB?characterEncoding=UTF-8&amp;useUnicode=TRUE&amp;autoReconnect=true&amp;failOverReadOnly=false
    12 bakdb.jdbc.username=root
    13 bakdb.jdbc.password=123456
    14 bakdb.jdbc.driverclass=com.mysql.jdbc.Driver
    15 bakdb.jdbc.ip=172.16.17.68
    16 bakdb.jdbc.dbname=bakDB
    两个数据库对应的属性文件

    (3)配置DynamicDataSource这个Bean(关键)。
    该DynamicDataSource的主要作用是以Map<Object, Object>的形式,来存储多个数据源。

     1 <!-- 配置可以存储多个数据源的Bean -->
     2 <bean id="dataSource" class="com.beebank.pub.datasource.DynamicDataSource">
     3     <property name="targetDataSources">
     4         <map key-type="java.lang.String">
     5             <entry key="dataSourceKey4TestDb" value-ref="c3p0DataSource4TestDb" />
     6             <entry key="dataSourceKey4BakDb" value-ref="c3p0DataSource4BakDb" />
     7         </map>
     8     </property>
     9     <property name="defaultTargetDataSource" ref="c3p0DataSource4HuihangDb" />
    10 </bean>        
    配置dataSource这个Bean

    (4)配置DataSourceInterceptor这个Bean(关键)。

    1 <!-- 配置切换数据源Key的拦截器 -->
    2 <bean id="dataSourceInterceptor" class="com.zjrodger.pub.datasource.DataSourceInterceptor"></bean>
    Bean—dataSourceInterceptor

    (5)利用AOP,配置控制数据源在特定条件下切换的切面(关键,重要)。

    注意要添加aop名字空间。

     1 <!-- 1.配置Spring框架自身提供的切面类 -->
     2 <tx:advice id="userTxAdvice" transaction-manager="transactionManager">
     3     <tx:attributes>
     4         <tx:method name="delete*" propagation="REQUIRED" read-only="false"
     5             rollback-for="java.lang.Exception" no-rollback-for="java.lang.RuntimeException" />
     6         <tx:method name="insert*" propagation="REQUIRED" read-only="false"
     7             rollback-for="java.lang.Exception" />
     8         <tx:method name="update*" propagation="REQUIRED" read-only="false"
     9             rollback-for="java.lang.Exception" />
    10         <tx:method name="find*" propagation="SUPPORTS" />
    11         <tx:method name="get*" propagation="SUPPORTS" />
    12         <tx:method name="select*" propagation="SUPPORTS" />
    13     </tx:attributes>
    14 </tx:advice>
    15 
    16 <!-- 2.配置用户自定义的切面,用于切换数据源Key -->
    17 <bean id="dataSourceInterceptor" class="com.zjrodger.pub.datasource.DataSourceInterceptor"></bean>    
    18 
    19 <!-- 3.(重要)配置Spring事务切面和自定义切面类,动态切换数据源,注意两切面的执行顺序 -->
    20 <aop:config>                    
    21     <!-- (1) Spring框架自身提供的切面 -->
    22     <aop:advisor advice-ref="userTxAdvice" pointcut="execution(public * com.zjrodger.*.service..*.*(..))" order="2"/>                                    
    23      
    24     <!-- (2) 用户自定义的切面,根据切入点,动态切换数据源。 -->        
    25     <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor" order="1">                                        
    26         <aop:before method="setdataSourceBakDb"  pointcut="execution(* com.zjrodger.bakdata.service..*.*(..))"/>                        
    27         <aop:before method="setdataSourceTestDb"  pointcut="execution(* com.zjrodger.datatobank.service..*.*(..))"/>
    28     </aop:aspect>        
    29 </aop:config>
    (重要)配置Spring事务切面和自定义切面类,动态切换数据源,注意两切面的执行顺序

    注意:

        A.注意上述两个切面中的order属性的配置。

        B.自定义切面类和Spring自带事务切面类(即<aop:advisor>元素)的执行的先后顺序要配置正确,否则就会导致导致数据源不能动态切换。

    在AOP中,当执行同一个切入点时,不同切面的执行先后顺序是由“每个切面的order属性”而定的,order越小,则该该切面中的通知越先被执行。

    上述<aop:config>元素中,引用了两个切面类:“userTxAdvice类”和“dataSourceAspect类”,其中<aop:advisor>是Spring框架自定义的切面标签。
    根据两个切面类order属性的定义,当程序执行时并且触发切入点后(即调用com.zjrodger.bakdata.service包及其子包下的方法),dataSourceAspect切面类中的setdatasourceBakDb()方通知法首先执行,之后才会执行userTxAdvice事务类中的相关通知方。

     说明

    切面类“DataSourceInterceptor”中有两个方法:setdataSourceTestDb()方法和setdataSourceBakDb()。
    1)当用户调用“com.zjrodger.bakdata.service”包及其子包下的方法X“之前”,系统会首先去调用setdataSourceBakDb()方法,设置当前数据源为bakDb的数据源,之后再去调用方法X。
    2)当用户调用“com.zjrodger.datatobank.service”或者“com.zjrodger.zxtobank.service”包及其子包下的方法Y之前,系统会首先去调调用setdataSourceTestDb()方法,设置当前的数据源为testDb数据库的数据源,之后再去调用方法Y。

    (6)完整的Spring配置文档

      1 <?xml version="1.0" encoding="UTF-8"?>
      2 <beans xmlns="http://www.springframework.org/schema/beans"
      3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
      4     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
      5     xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc"
      6     xmlns:task="http://www.springframework.org/schema/task"
      7     xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
      8         http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
      9         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     10         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
     11         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
     12         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
     13 
     14     <!-- 该配置为自动扫描配置的包下所有使用@Controller注解的类 -->
     15     <context:component-scan base-package="com.zjrodger" />
     16 
     17     <bean id="propertyConfigurer"
     18         class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
     19         <property name="location">
     20             <value>classpath:properties/dbconfig.properties</value>
     21         </property>
     22         <property name="fileEncoding" value="utf-8" />
     23     </bean>
     24     
     25     <!-- 备份库数据库数据源  -->
     26     <bean id="c3p0DataSource4BakDb" class="com.mchange.v2.c3p0.ComboPooledDataSource"
     27         destroy-method="close" depends-on="propertyConfigurer">
     28         <property name="driverClass" value="${bakdb.jdbc.driverclass}" />
     29         <property name="jdbcUrl" value="${bakdb.jdbc.url}" />
     30         <property name="user" value="${bakdb.jdbc.username}" />
     31         <property name="password" value="${bakdb.jdbc.password}" />
     32 
     33         <!-- 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
     34         <property name="initialPoolSize" value="10" />
     35         <!-- 连接池中保留的最小连接数。 -->
     36         <property name="minPoolSize" value="5" />
     37         <!-- 连接池中保留的最大连接数。Default: 15 -->
     38         <property name="maxPoolSize" value="100" />
     39         <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
     40         <property name="acquireIncrement" value="5" />
     41         <!-- 最大空闲时间,10秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
     42         <property name="maxIdleTime" value="10" />
     43         <!-- JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 
     44             如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
     45         <property name="maxStatements" value="0" />
     46         <!-- 连接池用完时客户调用getConnection()后等待获取连接的时间,单位:毫秒。超时后会抛出 SQLEXCEPTION,如果设置0,则无限等待。Default:0 -->
     47         <property name="checkoutTimeout" value="30000" />
     48     </bean>
     49     
     50 
     51     <!--Test数据库数据源 -->
     52     <bean id="c3p0DataSource4TestDb" class="com.mchange.v2.c3p0.ComboPooledDataSource"
     53         destroy-method="close" depends-on="propertyConfigurer">
     54         <property name="driverClass" value="${jdbc.driverclass}" />
     55         <property name="jdbcUrl" value="${jdbc.url}" />
     56         <property name="user" value="${jdbc.username}" />
     57         <property name="password" value="${jdbc.password}" />
     58 
     59         <!-- 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
     60         <property name="initialPoolSize" value="10" />
     61         <!-- 连接池中保留的最小连接数。 -->
     62         <property name="minPoolSize" value="5" />
     63         <!-- 连接池中保留的最大连接数。Default: 15 -->
     64         <property name="maxPoolSize" value="100" />
     65         <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
     66         <property name="acquireIncrement" value="5" />
     67         <!-- 最大空闲时间,10秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
     68         <property name="maxIdleTime" value="10" />
     69         <!-- JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 
     70             如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
     71         <property name="maxStatements" value="0" />
     72         <!-- 连接池用完时客户调用getConnection()后等待获取连接的时间,单位:毫秒。超时后会抛出 SQLEXCEPTION,如果设置0,则无限等待。Default:0 -->
     73         <property name="checkoutTimeout" value="30000" />
     74     </bean>
     75     
     76     <!-- 配置可以存储多个数据源的Bean -->
     77     <bean id="dataSource" class="com.zjrodger.pub.datasource.DynamicDataSource">
     78         <property name="targetDataSources">
     79             <map key-type="java.lang.String">
     80                 <entry key="dataSourceKey4TestDb" value-ref="c3p0DataSource4TestDb" />
     81                 <entry key="dataSourceKey4BakDb" value-ref="c3p0DataSource4BakDb" />
     82             </map>
     83         </property>
     84         <property name="defaultTargetDataSource" ref="c3p0DataSource4TestDb" />
     85     </bean>        
     86 
     87     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
     88         <!-- <property name="dataSource" ref="c3p0DataSource" /> -->
     89         <property name="dataSource" ref="dataSource" />
     90 
     91         <property name="mapperLocations" value="classpath*:com/zjrodger/**/dao/xml/*.xml" />
     92         <!-- 添加分页插件 -->
     93         <property name="plugins">
     94             <list>
     95                 <bean class="com.github.pagehelper.PageHelper">
     96                     <property name="properties">
     97                         <props>
     98                             <prop key="dialect">mysql</prop>
     99                             <prop key="offsetAsPageNum">true</prop>
    100                             <prop key="rowBoundsWithCount">true</prop>
    101                             <prop key="pageSizeZero">true</prop>
    102                             <prop key="reasonable">true</prop>
    103                         </props>
    104                     </property>
    105                 </bean>
    106             </list>
    107         </property>
    108 
    109     </bean>
    110 
    111     <bean id="transactionManager"
    112         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    113         <!-- <property name="dataSource" ref="c3p0DataSource" /> -->
    114         <property name="dataSource" ref="dataSource" />
    115     </bean>
    116 
    117     <!-- 注解驱动,使spring的controller全部生效 -->
    118     <mvc:annotation-driven />
    119     <!-- 注解驱动,是spring的task全部生效 -->
    120     <task:annotation-driven />
    121 
    122 
    123     <aop:aspectj-autoproxy expose-proxy="true" />    
    124     <tx:annotation-driven transaction-manager="transactionManager"/>        
    125 
    126     <!-- Spring声明式事务切面 -->
    127     <tx:advice id="userTxAdvice" transaction-manager="transactionManager">
    128         <tx:attributes>
    129             <tx:method name="delete*" propagation="REQUIRED" read-only="false"
    130                 rollback-for="java.lang.Exception" no-rollback-for="java.lang.RuntimeException" />
    131             <tx:method name="insert*" propagation="REQUIRED" read-only="false"
    132                 rollback-for="java.lang.Exception" />
    133             <tx:method name="update*" propagation="REQUIRED" read-only="false"
    134                 rollback-for="java.lang.Exception" />
    135             <tx:method name="find*" propagation="SUPPORTS" />
    136             <tx:method name="get*" propagation="SUPPORTS" />
    137             <tx:method name="select*" propagation="SUPPORTS" />
    138         </tx:attributes>
    139     </tx:advice>
    140 
    141     <aop:config>                    
    142         <!-- Spring框架自身提供的切面 -->
    143         <aop:advisor advice-ref="userTxAdvice" pointcut="execution(public * com.zjrodger.*.service..*.*(..))" order="2"/>                                    
    144     
    145         <!-- 用户自定义的切面,根据切入点,动态切换数据源。 -->        
    146         <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor" order="1">                                        
    147             <aop:before method="setdataSourceBakDb"  pointcut="execution(* com.zjrodger.bakdata.service..*.*(..))"/>                        
    148             <aop:before method="setdataSourceTestDb"  pointcut="execution(* com.zjrodger.datatobank.service..*.*(..))"/>
    149             <aop:before method="setdataSourceTestDb"  pointcut="execution(* com.zjrodger.zxtobank.service..*.*(..))"/>                        
    150         </aop:aspect>        
    151     </aop:config>
    152     
    153     <!-- 配置切换数据源Key的拦截器 -->
    154     <bean id="dataSourceInterceptor" class="com.zjrodger.pub.datasource.DataSourceInterceptor"></bean>    
    155     
    156     <!-- mybatis配置 -->
    157     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    158         <property name="basePackage" value="com.zjrodger.pub.dao,com.zjrodger.zxtobank.dao,com.zjrodger.bakdata.dao" />
    159         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    160     </bean>        
    161 </beans>
    applicationContext.xml

    至此,MyBatis多数据源的配置完毕,之后在自己的环境下进行测试,结果测试通过。

    要特别注意自定义AOP切面与Spring自带的事务切面的执行顺序,即注意<aop:config>中的配置部分,否则,很容易会出现动态切换数据源失败的现象。

    【同专题博客内连接】

    1、Order属性决定了不同切面类中通知执行的先后顺序
    http://www.cnblogs.com/zjrodger/p/5633922.html

    2、不定义Order属性,通过切面类的定义顺序来决定通知执行的先后顺序
    http://www.cnblogs.com/zjrodger/p/5633951.html

    【其他参考连接】

    1、《Spring中事务与aop的先后顺序问题》http://my.oschina.net/HuifengWang/blog/304188

  • 相关阅读:
    Android基础知识之拼写检查框架
    Android USB配件模式
    Android基础知识之Manifest中的Intent-filter元素
    如何使Android应用支持多种屏幕分辨率
    优惠券系统设计(系统设计设计篇)
    优惠券系统设计(数据库设计篇)
    优惠券系统设计(产品设计篇)
    java 文件断点续传实现原理
    java sleep和wait的区别的疑惑?
    java中的中断Thread.interrupt()意味着什么?
  • 原文地址:https://www.cnblogs.com/zjrodger/p/5627878.html
Copyright © 2011-2022 走看看