实现Mybatis接口模式下的数据库调用分离

目标:

多项目,多数据库,多连接池,程序级动态切换数据库调用

 

环境基础:

         框架:SPRING+MYBATIS+MYSQL/ORACLE

 

设想:

Mapper分包处理不同的库

BaseDao分包处理不同的库

BaseService分包处理不同的库

 

实现:

多个数据源管理结构:

 

 配置文件:

 1 <!-- MyBatis begin && Alias-->
 2     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 3         <property name="dataSource" ref="multipleDataSource"/>
 4         <property name="typeAliasesPackage" value="com.thinkgem.jeesite,com.qysxy.product"/>
 5         <!--com.thinkgem.jeesite.common.persistence.BaseEntity-->
 6         <property name="typeAliasesSuperType" value="com.thinkgem.jeesite.common.persistence.SuperEntity"/>
 7         <property name="mapperLocations" value="classpath:/mappings/**/*.xml"/>
 8         <property name="configLocation" value="classpath:/mybatis-config.xml"></property>
 9     </bean>
10 
11     <!--多数据源管理配置-->
12     <bean id="multipleDataSource" class="com.qysxy.framework.spring.support.MultipleDataSource">
13         <property name="defaultTargetDataSource" ref="dataSource"/>
14         <property name="targetDataSources">
15             <map>
16                 <entry key="MYSQL-PROJECT1" value-ref="dataSource"/>
17                 <entry key="MYSQL-PROJECT2" value-ref="dataSource-project2"/>
18             </map>
19         </property>
20     </bean>
21 
22 
23     <!-- 数据源配置, 使用 BoneCP 数据库连接池 -->
24     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
25         <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
26         <property name="driverClassName" value="${jdbc.driver}" />
27         
28         <!-- 基本属性 url、user、password -->
29         <property name="url" value="${jdbc.url}" />
30         <property name="username" value="${jdbc.username}" />
31         <property name="password" value="${jdbc.password}" />
32         
33         <!-- 配置初始化大小、最小、最大 -->
34         <property name="initialSize" value="${jdbc.pool.init}" />
35         <property name="minIdle" value="${jdbc.pool.minIdle}" /> 
36         <property name="maxActive" value="${jdbc.pool.maxActive}" />
37         
38         <!-- 配置获取连接等待超时的时间 -->
39         <property name="maxWait" value="60000" />
40         
41         <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
42         <property name="timeBetweenEvictionRunsMillis" value="60000" />
43         
44         <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
45         <property name="minEvictableIdleTimeMillis" value="300000" />
46         
47         <property name="validationQuery" value="${jdbc.testSql}" />
48         <property name="testWhileIdle" value="true" />
49         <property name="testOnBorrow" value="false" />
50         <property name="testOnReturn" value="false" />
51         
52         <!-- 打开PSCache,并且指定每个连接上PSCache的大小(Oracle使用)
53         <property name="poolPreparedStatements" value="true" />
54         <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> -->
55         
56         <!-- 配置监控统计拦截的filters -->
57         <property name="filters" value="stat" /> 
58     </bean>
59 
60     <!-- 数据源配置, 使用 BoneCP 数据库连接池,针对平台插拔系统2 -->
61     <bean id="dataSource-project2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
62         <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
63         <property name="driverClassName" value="${jdbc.driver}" />
64 
65         <!-- 基本属性 url、user、password -->
66         <property name="url" value="${jdbc.url.project2}" />
67         <property name="username" value="${jdbc.username}" />
68         <property name="password" value="${jdbc.password}" />
69 
70         <!-- 配置初始化大小、最小、最大 -->
71         <property name="initialSize" value="${jdbc.pool.init}" />
72         <property name="minIdle" value="${jdbc.pool.minIdle}" />
73         <property name="maxActive" value="${jdbc.pool.maxActive}" />
74 
75         <!-- 配置获取连接等待超时的时间 -->
76         <property name="maxWait" value="60000" />
77 
78         <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
79         <property name="timeBetweenEvictionRunsMillis" value="60000" />
80 
81         <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
82         <property name="minEvictableIdleTimeMillis" value="300000" />
83 
84         <property name="validationQuery" value="${jdbc.testSql}" />
85         <property name="testWhileIdle" value="true" />
86         <property name="testOnBorrow" value="false" />
87         <property name="testOnReturn" value="false" />
88 
89         <!-- 打开PSCache,并且指定每个连接上PSCache的大小(Oracle使用)
90         <property name="poolPreparedStatements" value="true" />
91         <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> -->
92 
93         <!-- 配置监控统计拦截的filters -->
94         <property name="filters" value="stat" />
95     </bean>
96 
97 <!--切面注解驱动-->
98     <aop:aspectj-autoproxy proxy-target-class="false"/>

 

 1 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 2 
 3 /**
 4  * @author fuguangli
 5  * @description 数据库选择路由
 6  * @Create date:    2017/2/24
 7  */
 8 public class MultipleDataSource extends AbstractRoutingDataSource {
 9     @Override
10     protected Object determineCurrentLookupKey() {
11         return DatabaseContextHolder.getDataBaseType();
12     }
13 }
 1 public class DatabaseContextHolder {
 2     public static String DATABASE_MYSQL_PROJECT1 = "MYSQL-PROJECT1";//默认
 3     public static String DATABASE_MYSQL_PROJECT2 = "MYSQL-PROJECT2";
 4 
 5     private static final ThreadLocal<String> contextHolder = new ThreadLocal();
 6 
 7     public static void setDataBaseType(String context) {
 8         contextHolder.set(context);
 9     }
10 
11     public static String getDataBaseType() {
12         if (contextHolder.get() == null) {
13             return DATABASE_MYSQL_PROJECT1;
14         }
15 
16         return contextHolder.get();
17     }
18 
19     public static void clearDatabaseType() {
20         contextHolder.remove();
21     }
22 
23 }
1 @Test
2     public void changeDataSource() {
3         Long time  = System.currentTimeMillis();
4         System.out.println("start ="+time);
5         DatabaseContextHolder.setDataBaseType(DatabaseContextHolder.DATABASE_MYSQL_PROJECT2);
6         List<Object> list = testService.findBySql("select * from jee_user");
7         System.out.println("end ="+(System.currentTimeMillis()-time));//185-126
8     }

此时,可以进行切换数据库,但是还没有做到真正的程序自动切换,此时利用spring的AOP进行分包检测,自动切换数据源。

 

原理:

目前,spring AOP的使用条件:

1、代理目标必须是实现类

2、代理的方法是实现的接口的方法

3、程序需要调用实现的接口方法

4、代理的类的所有上层接口不能有过代理

 

还有,在本次框架中需要注意的是,mybatis采用的接口调用,所以mybatis框架对DAO接口已经进行了一次java反射代理,所以,为了动态的切换数据源,需要重新写一个接口和实现类调用DAO方法,然后对此类进行代理。

 

此时有两种方法实现代理:

一、代理mybatis的DAO接口

基本实现就是,你需要写一个接口和实现类去封装mybatis的调用接口,很像是我们经常做的service层。

 1 import com.qysxy.framework.spring.support.DatabaseContextHolder;
 2 import org.aspectj.lang.JoinPoint;
 3 import org.aspectj.lang.annotation.*;
 4 import org.springframework.stereotype.Component;
 5 
 6 import java.lang.annotation.Annotation;
 7 
 8 /**
 9  * @author fuguangli
10  * @description 切面通知类
11  * @Create date:    2017/2/24
12  */
13 @Component("dataSourceAdvice ")
14 @Aspect
15 public class DataSourceAdvice{
16 
17     @Pointcut("execution(*com.thinkgem.jeesite.modules.test.service.*Service.*(..))")
18     private   void pointcut(){}
19 
20 //    @Pointcut("@annotation(daoAOP)")
21 //    private   void pointcut(DaoAOP daoAOP){}
22 
23     /**
24      * 在调用方法执行前执行,不能阻止方法的调用。
25      * @param joinPoint
26      */
27     @Before("pointcut()")
28     public void doBefore(JoinPoint joinPoint) {
29        DatabaseContextHolder.setDataBaseType(DatabaseContextHolder.DATABASE_MYSQL_PROJECT2);
30 
31         System.out.println("--------调用数据库之前切换设置数据源--------");
32     }
33 }

二、自定义注解,实现切面

其实原理和上面是一样的,也是需要写一层去封装mybatis调用接口,然后做切面配置的时候,加上自定义注解。

 

贴上自定义注解:

 1 import java.lang.annotation.*;
 2 
 3 /**
 4  * @author fuguangli
 5  * @description 切面注解
 6  * @Create date:    2017/2/27
 7  */
 8 @Target(ElementType.METHOD)
 9 @Retention(RetentionPolicy.RUNTIME)
10 @Documented
11 public @interface DaoAOP {
12     String value() default "";
13 }

 

posted @ 2017-02-27 17:10  构建巨人肩膀  阅读(802)  评论(0编辑  收藏  举报