因为业务需求,这几天一直在研究Spring+MyBatis多数据源切换。按照网上的各种例子,改了又改,就是切换不成功。最后在其他同事的提醒下,终于发现并解决了问题。现在将多数据源切换的配置和出现并解决的问题记录下来,为自己存一份记忆,也为后来人提供帮助。
一、多数据源的配置
1、两个类
这两个类的类名可以根据自己的编码习惯命名,我的命名及实现代码如下:
(1)、DataSourceContextHolder:用于进行数据源的获取、设置及还原
package com.cpms.trasen.common.tk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源设置类
*
* @author bigdata-cp
*
*/
public class DataSourceContextHolder {
// 日志,与多数据源切换无关
private final static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);
// 用于切换数据源的线程
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
// 设置数据源
public static void setDataSourceType(String dataSourceType) {
logger.info("DataSourceContextHolder.setDataSourceType start");
contextHolder.set(dataSourceType);
logger.info("DataSourceContextHolder.setDataSourceType end");
}
// 获取现在的数据源
public static String getDataSourceType() {
logger.info("implement DataSourceContextHolder.getDataSourceType");
return contextHolder.get();
}
// 还原(清除设置的)数据源
public static void clearDataSourceType() {
logger.info("implement DataSourceContextHolder.clearDataSourceType");
contextHolder.remove();
}
}
(2)、DynamicDataSource:需要继承AbstractRoutingDataSource抽象类用于实现数据源的切换
package com.cpms.trasen.common.tk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
private final static Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
logger.info("implement DynamicDataSource.determineCurrentLookupKey");
System.out.println("此时获取到的数据源为:" + DataSourceContextHolder.getDataSourceType());
return DataSourceContextHolder.getDataSourceType();
}
}
2、配置文件
SSM框架自带的如事物、日志、别名等配置这里就不多说了,一搜一大把。这里主要贴多数据源相关的配置代码。
(1)、数据源文件properties文件:我这里都是SqlServer数据库,如果有其他类型的数据库,手动添加驱动、连接方式和用户密码就行。
#SQLServerDriver
driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
#dataSourceA
dataSourceAurl=jdbc:sqlserver://localhost:1433;DatabaseName=trasen_clinicalPathMonitoring
dataSourceAuname=sa
dataSourceApwd=1
#dataSourceB
dataSourceBurl=jdbc:sqlserver://192.168.2.108:1433;DatabaseName=trasen_clinicalPathMonitoring
dataSourceBuname=sa
dataSourceBpwd=1
(2)、spring-mybatis配置文件:基本配置和多数据源配置
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 扫描service层 -->
<mvc:annotation-driven />
<context:component-scan base-package="com.cpms..trasen.*.server" use-default-filters="false" />
<!-- 引入数据库配置文件 -->
<context:property-placeholder location="classpath:sqlServer.properties" />
<bean id="parentDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass">
<value>${driverClassName}</value>
</property>
<!-- 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="5" />
<!-- 连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="200" />
<!-- 连接池中保留的最小连接数。 -->
<property name="minPoolSize" value="5" />
<!-- 配置当连接池所有连接用完时应用程序getConnection的等待时间 -->
<property name="checkoutTimeout" value="30000" />
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="5" />
<!-- 最大空闲时间,x秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="601" />
<!-- 每x秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod" value="600" />
<!-- 连接池在获得新连接失败时重试的次数,如果小于等于0则无限重试直至连接获得成功。Default: 30 -->
<property name="acquireRetryAttempts" value="5" />
<!-- 两次连接中间隔时间,连接池在获得新连接时的间隔时间。default: 1000 单位ms -->
<property name="acquireRetryDelay" value="1000" />
<!-- 测试连接
<property name="preferredTestQuery">
<value>SELECT 1</value>
</property> -->
</bean>
<!-- 定义数据源Bean -->
<bean id="dataSourceA" parent="parentDataSource">
<property name="jdbcUrl">
<value>${dataSourceAurl}</value>
</property>
<property name="user">
<value>${dataSourceAuname}</value>
</property>
<property name="password">
<value>${dataSourceApwd}</value>
</property>
</bean>
<bean id="dataSourceB" parent="parentDataSource">
<property name="jdbcUrl">
<value>${dataSourceBurl}</value>
</property>
<property name="user">
<value>${dataSourceBuname}</value>
</property>
<property name="password">
<value>${dataSourceBpwd}</value>
</property>
</bean>
<!-- 配置dataSource管理key值和value值对应,默认选择dataSourceA ,其他配置按照正常的spring mvc 配置即可。 -->
<bean id="dataSourceSwitcher" class="com.cpms.trasen.common.tk.DynamicDataSource">
<!-- 默认数据源 -->
<property name="defaultTargetDataSource" ref="dataSourceA"></property>
<!-- 通过key-value的形式来关联数据源 -->
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSourceA" value-ref="dataSourceA"></entry>
<entry key="dataSourceB" value-ref="dataSourceB"></entry>
</map>
</property>
</bean>
<!-- 注册SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSourceSwitcher" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!-- 自动扫描mappers.xml文件 -->
<property name="mapperLocations" value="classpath:com/cpms/trasen/*/dao/mappings/*.xml" />
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cpms.trasen.*.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
</beans>
3、数据源的切换
项目需求在用户登录成功后,根据用户切换对应的数据源。博主就是直接写在用于登录的service实现类的方法中,该方法调用了一个WebService用于验证用户的登录信息,所以线程不止一个。在设置数据源后的线程的生命周期,在切换数据源之前结束了,进入了另一个线程,所以获取的数据源为null导致切换失败。最后,将设置数据源的方法转移到了验证session过期的AOP中,既主线程中,才成功切换数据源。
调用设置数据源的方法如下:数据源字符串也可设置为类变量,请根据应用场景进行设置。
// "xxx"为需要切换的数据源字符串,该字符串为xml中配置的多数据源id
DataSourceContextHolder.setDataSourceType("xxx");
多数据源的配置与切换其实内容很简单,网上的例子都是可以行得通的。但关键的问题是每个项目遇到的情况都会不同,所以导致出现的问题也会不同,需要各自发现和解决。看到的各位有什么不懂的欢迎留言一起探讨。
每天积累一点,付出总会得到回报。