实现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 }