<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!-- 引入配置文件 -->
<bean id="propertyConfigurer"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:db.properties"/>
</bean>
<!-- mp数据库源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<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}" />
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${db.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${db.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${db.maxWait}"></property>
</bean>
<!-- VOS数据库源 -->
<bean id="vosDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${vos.jdbc.url}" />
<property name="username" value="${vos.jdbc.username}" />
<property name="password" value="${vos.jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${vos.jdbc.initialSize}" />
<!-- 连接池最大数量 -->
<property name="maxActive" value="${vos.jdbc.maxActive}" />
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${vos.jdbc.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${vos.jdbc.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${vos.jdbc.maxWait}"></property>
</bean>
<!-- 数据库连接池 -->
<bean id="dynamicDataSource" class="com.mobiprima.sip.common.util.DynamicDataSource">
<!--目标数据源,名称不能改-->
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSource" key="dataSource" />
<entry value-ref="vosDataSource" key="vosDataSource" />
</map>
</property>
<!-- 默认使用mpDataSource的数据源 -->
<property name="defaultTargetDataSource" ref="dataSource" />
</bean>
<!--配置会话工厂-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--扫描mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis.xml" />
<!--配置数据库连接池-->
<property name="dataSource" ref="dynamicDataSource" />
<!--配置事务-->
<property name="transactionFactory">
<bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
</property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--Repository注解-->
<property name="annotationClass" value="org.springframework.stereotype.Repository" />
<property name="basePackage" value="com.mobiprima.sip.common.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
</beans>
Spring提供了AbstractRoutingDataSource抽象类实现根据路由找到数据源,里面提供了afterPropertiesSet方法,在数据库配置文件初始化后执行,如下图:
从代码中可以出首先会判断目标数据源是否为空,为空则抛出异常,不为空,则将数据源存放到一个map当中。这里的关键在于targetDataSources属性。方法一是直接将要切换的数据源bean添加该map中,因为是固定的,所以后续也不需要修改了。而方法二是在determineCurrentLookupKey()中将数据源bean动态的生成,然后添加到targetDataSources map中,因为是动态生成添加,所以需要调用afterPropertiesSet()方法,通知spring容器更新。AbstractRoutingDataSource中方法引用的都是resolvedDataSources该属性,所以AbstractRoutingDataSource将targetDataSources的键值对信息都添加到resolvedDataSources属性中,以便后续的调用。
2.连接数据库
spring容器连接哪个数据源,是由该方法决定
而具体选择哪个数据源又是由determineCurrentLookupKey()方法的返回值决定的,该方法需要我们继承AbstractRoutingDataSource来重写。该方法返回一个key,该key就是数据源bean的beanName,我们根据key去resolvedDataSources中取到DataSource,然后将DataSource返回
DynamicDataSource.java
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDbType();
}
}
DataSourceContextHolder.java
public class DataSourceContextHolder {
public static final String DATA_SOURCE_CASH_LOAN_ADMIN = "dataSource";
public static final String DATA_SOURCE_VOS = "vosDataSource";
//用ThreadLocal来设置当前线程使用哪个dataSource
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDbType(String dbType) {
//void set(Object value)设置当前线程的线程局部变量的值。
contextHolder.set(dbType);
}
public static String getDbType() {
//public Object get()该方法返回当前线程所对应的线程局部变量。
return ((String) contextHolder.get());
}
public static void clearDbType() {
//public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。 需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
contextHolder.remove();
}
}
值得注意的是在CustomerContextHolder.java中使用了ThreadLocal类的set方法来设置当前线程要选择的dataSource,看一下set方法的源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
显而易见,获取当前线程,并且使用一个hashmap把需要存储的值设置进去。因为tomcat是用的线程池来处理每个请求,所以用ThreadLocal可以保证线程安全问题。
参考:https://blog.csdn.net/u013034378/article/details/81661706