MyBatis 多数据源切换
一、注解式切换
多数据库设置
SpringMVC+myBatis +dbcp环境
原理
借助spring的 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource这个抽象类实现。
这是一个路由数据源的东西。有一个方法determineCurrentLookupKey
/** * Determine the current lookup key. This will typically be * implemented to check a thread-bound transaction context. * <p>Allows for arbitrary keys. The returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolveSpecifiedLookupKey} method. */ protected abstract Object determineCurrentLookupKey();
每次去连数据库的时候,spring会调用这个方法去找对应的数据源。返回值即对应的数据源的LookUpKey.
我们通过重写这个方法,来实现获取自己想要的数据源来操作数据库。
1、首先web.xml里面需要配置spring扫描

3、applicationContext.xml的配置如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- z自动扫描注解--> <context:component-scan base-package="com.mmall.service.*" annotation-config="true"/> <!--<context:annotation-config/>--> <aop:aspectj-autoproxy/> <!-- 配置文件分离--> <import resource="applicationContext-datasource.xml"/> </beans>
4、applicationContext-datasource.xml配置如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.mmall" annotation-config="true"/> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="2"/> <property name="ignoreUnresolvablePlaceholders" value="true"/> <property name="locations"> <list> <value>classpath:datasource.properties</value> </list> </property> <property name="fileEncoding" value="utf-8"/> </bean> <!-- ① 第一个数据源--> <bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db.driverClassName}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.username}"/> <property name="password" value="${db.password}"/> <!-- 连接池启动时的初始值 --> <property name="initialSize" value="${db.initialSize}"/> <!-- 连接池的最大值 --> <property name="maxActive" value="${db.maxActive}"/> <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --> <property name="maxIdle" value="${db.maxIdle}"/> <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 --> <property name="minIdle" value="${db.minIdle}"/> <!-- 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制 --> <property name="maxWait" value="${db.maxWait}"/> <!--#给出一条简单的sql语句进行验证 --> <!--<property name="validationQuery" value="select getdate()" />--> <property name="defaultAutoCommit" value="${db.defaultAutoCommit}"/> <!-- 回收被遗弃的(一般是忘了释放的)数据库连接到连接池中 --> <!--<property name="removeAbandoned" value="true" />--> <!-- 数据库连接过多长时间不用将被视为被遗弃而收回连接池中 --> <!--<property name="removeAbandonedTimeout" value="120" />--> <!-- #连接的超时时间,默认为半小时。 --> <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/> <!--# 失效检查线程运行时间间隔,要小于MySQL默认--> <property name="timeBetweenEvictionRunsMillis" value="40000"/> <!--# 检查连接是否有效--> <property name="testWhileIdle" value="true"/> <!--# 检查连接有效性的SQL语句--> <property name="validationQuery" value="SELECT 1 FROM dual"/> </bean> <!-- ② 第二个数据源--> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db.driverClassName}"/> <property name="url" value="${db2.url}"/> <property name="username" value="${db2.username}"/> <property name="password" value="${db2.password}"/> <!-- 连接池启动时的初始值 --> <property name="initialSize" value="${db.initialSize}"/> <!-- 连接池的最大值 --> <property name="maxActive" value="${db.maxActive}"/> <!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 --> <property name="maxIdle" value="${db.maxIdle}"/> <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 --> <property name="minIdle" value="${db.minIdle}"/> <!-- 最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制 --> <property name="maxWait" value="${db.maxWait}"/> <!--#给出一条简单的sql语句进行验证 --> <!--<property name="validationQuery" value="select getdate()" />--> <property name="defaultAutoCommit" value="${db.defaultAutoCommit}"/> <!-- 回收被遗弃的(一般是忘了释放的)数据库连接到连接池中 --> <!--<property name="removeAbandoned" value="true" />--> <!-- 数据库连接过多长时间不用将被视为被遗弃而收回连接池中 --> <!--<property name="removeAbandonedTimeout" value="120" />--> <!-- #连接的超时时间,默认为半小时。 --> <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/> <!--# 失效检查线程运行时间间隔,要小于MySQL默认--> <property name="timeBetweenEvictionRunsMillis" value="40000"/> <!--# 检查连接是否有效--> <property name="testWhileIdle" value="true"/> <!--# 检查连接有效性的SQL语句--> <property name="validationQuery" value="SELECT 1 FROM dual"/> </bean> <!-- ③ 配置自定义数据源获取--> <bean id="dataSource" class="com.mmall.common.ChooseDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- dataSource2 --> <entry key="dataSource2" value-ref="dataSource2"/> <!-- dataSource1 --> <entry key="${defaultDataSourceKey}" value-ref="dataSource1"/> </map> </property> <property name="defaultTargetDataSource" ref="dataSource1"/> </bean> <!-- ④ 连接路由数据源获取--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"></property> <!-- 分页插件 --> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <value> dialect=mysql </value> </property> </bean> </array> </property> </bean> <bean name="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.mmall.dao"/> </bean> <!-- 使用@Transactional进行声明式事务管理需要声明下面这行 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" /> <!-- 事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> <property name="rollbackOnCommitFailure" value="true"/> </bean> <!-- ⑤ 根据注解调用不同数据库--> <!-- 为业务逻辑层的方法解析@DataSource注解 为当前线程的routeholder注入数据源key --> <bean id="dataSourceAspect" class="com.mmall.common.DataSourceAspect" /> <aop:config proxy-target-class="true"> <aop:aspect id="dataSourceAspect" ref="dataSourceAspect" order="1"> <aop:pointcut id="tx" expression="execution(* com.mmall.service.*.*(..)) "/> <aop:before pointcut-ref="tx" method="before" /> </aop:aspect> </aop:config> </beans>
配置文件中的步骤我已经用○圆圈标记出来
5、数据库配置文件如下:
db.driverLocation=D:\\softInstallTo\\maven\\maven_repository\\newrepository\\mysql\\mysql-connector-java\\5.1.6\\mysql-connector-java-5.1.6.jar db.driverClassName=com.mysql.jdbc.Driver # dataBase 1 #db.url=jdbc:mysql://10.87.32.44:3306/mmall?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false db.url=jdbc:mysql://localhost:3306/mmall?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false db.username=root db.password=wanghao # dataBase 2 db2.url=jdbc:mysql://192.168.108.128:3306/test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false db2.username=root db2.password=root db.initialSize = 20 db.maxActive = 50 db.maxIdle = 20 db.minIdle = 10 db.maxWait = 10 db.defaultAutoCommit = true db.minEvictableIdleTimeMillis = 3600000
可以看到我有两个数据源。两个数据源都是MySql测试使用。
6、 ChooseDataSource 继承 AbstractRoutingDataSource 并实现了 determineCurrentLookupKey
package com.mmall.common; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class ChooseDataSource extends AbstractRoutingDataSource { /** * 获取与数据源相关的key * 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值 * 在通过determineTargetDataSource获取目标数据源时使用 */ @Override protected Object determineCurrentLookupKey() { return HandleDataSource.getDataSource(); } }
7、DataSourceAspect 切面 put dataSource
package com.mmall.common; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; public class DataSourceAspect { /** * 在dao层方法之前获取datasource对象之前在切面中指定当前线程数据源路由的key */ public void before(JoinPoint point){ Object target = point.getTarget(); System.out.println(target.toString()); String method = point.getSignature().getName(); System.out.println(method); Class<?>[] classz = target.getClass().getInterfaces(); // 获取方法的参数??参数类型? Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()) .getMethod().getParameterTypes(); try{ Method m = classz[0].getMethod(method, parameterTypes); System.out.println(m.getAnnotations().toString()); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource data = m.getAnnotation(DataSource.class); System.out.println("调用方法:"+m.getName()); System.out.println("用户选择数据库库类型:"+data.value()); HandleDataSource.putDataSource(data.value()); }else{ //默认的数据库源Key //String defaultDataSourceKey = PropertiesUtil.getValue("defaultDataSourceKey"); String defaultDataSourceKey = "dataSource1"; //当前线程的数据源key String nowDataSource = HandleDataSource.getDataSource(); if(defaultDataSourceKey==null){ defaultDataSourceKey = "dataSource1"; } if(nowDataSource!=null&&!nowDataSource.equals(defaultDataSourceKey)){ System.out.println("无@DataSource默认数据库类型:"+defaultDataSourceKey); HandleDataSource.putDataSource(defaultDataSourceKey); } } }catch (Exception e){ e.printStackTrace(); } } }
8、DataSource 注解类
package com.mmall.common; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /*** * RUNTIME * 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。 * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DataSource { String value(); }
9、HandleDataSource 线程保存数据源
package com.mmall.common; /** * 保存当前线程数据源的key */ public class HandleDataSource { public static final ThreadLocal<String> holder = new ThreadLocal<String>(); /** * 绑定当前线程数据源路由的key * @param datasource */ public static void putDataSource(String datasource) { holder.set(datasource); } /** * 获取当前线程的数据源路由的key * @return */ public static String getDataSource() { return holder.get(); } }
其实看源代码很好理解的。

博客参考自:

浙公网安备 33010602011771号