这篇文章主要介绍了SpringMVC Mybatis配置多个数据源并切换代码详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
最近公司一个项目需要连接两个数据库(A和B)操作,有的模块查询A库,有的模块查询B库,因此需要改造下,项目后台用的是SpringMVC+Mybatis+MysqL架构,折腾了两天后终于搞定了,在这里记录过改造过程。
使用场景
多数据源的使用的场景一般有:
- 主从数据库切换
- 读写分离
- 兼容旧库
实现原理
Spring2.x的版本中采用Proxy模式,就是在方案中实现一个虚拟的数据源,并且用它来封装数据源选择逻辑,这样就可以有效地将数据源选择逻辑从Client中分离出来。Client提供选择所需的上下文,由虚拟的DynamicDataSource根据Client提供的上下文来实现数据源的选择。
具体的实现是虚拟的DynamicDataSource仅需继承AbstractRoutingDataSource实现determineCurrentLookupKey(),该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到则用默认的resolvedDefaultDataSource。
详细实现过程
配置文件里需要配置多个数据源,改造后主要配置如下:
<bean id="dataSourceTargetA" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" scope="singleton"> <property name="driverClass" value="${jdbc.a.driverClassName}" /> <property name="jdbcUrl" value="${jdbc.a.url}" /> <property name="user" value="${jdbc.a.username}" /> <property name="password" value="${jdbc.a.password}" /> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> <property name="acquireIncrement" value="${c3p0.acquireIncrement}"></property> <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> <property name="initialPoolSize" value="${c3p0.initialPoolSize}"></property> <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> <property name="maxIdleTime" value="${c3p0.maxIdleTime}"></property> <!--连接池中保留的最大连接数。Default: 15 --> <property name="maxPoolSize" value="${c3p0.maxPoolSize}"></property> <property name="minPoolSize" value="${c3p0.minPoolSize}"></property> <!--两次连接中间隔时间,单位毫秒。Default: 1000 --> <property name="acquireRetryDelay" value="1000"></property> <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> <property name="acquireRetryAttempts" value="60"></property> <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false --> <property name="breakAfterAcquireFailure" value="false"></property> </bean> <bean id="dataSourceTargetB" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" scope="singleton"> <property name="driverClass" value="${jdbc.b.driverClassName}" /> <property name="jdbcUrl" value="${jdbc.b.url}" /> <property name="user" value="${jdbc.b.username}" /> <property name="password" value="${jdbc.b.password}" /> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> <property name="acquireIncrement" value="${c3p0.acquireIncrement}"></property> <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> <property name="initialPoolSize" value="${c3p0.initialPoolSize}"></property> <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> <property name="maxIdleTime" value="${c3p0.maxIdleTime}"></property> <!--连接池中保留的最大连接数。Default: 15 --> <property name="maxPoolSize" value="${c3p0.maxPoolSize}"></property> <property name="minPoolSize" value="${c3p0.minPoolSize}"></property> <!--两次连接中间隔时间,单位毫秒。Default: 1000 --> <property name="acquireRetryDelay" value="1000"></property> <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> <property name="acquireRetryAttempts" value="60"></property> <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false --> <property name="breakAfterAcquireFailure" value="false"></property> </bean> <!-- 动态数据源 --> <bean id="dynamicDataSource" class="com.test.util.DynamicDataSource" > <property name="targetDataSources"> <map key-type="java.lang.String"> <entry value-ref="dataSourceTargetA" key="dataSourceTargetA"></entry> <entry value-ref="dataSourceTargetB" key="dataSourceTargetB"></entry> </map> </property> <property name="defaultTargetDataSource" ref="dataSourceTargetA" > </property> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dynamicDataSource" /> </bean> <!-- MyBatis ORM operation class --> <bean id="sqlSessionFactory" class="org.mybatis.spring.sqlSessionfactorybean"> <property name="dataSource" ref="dynamicDataSource" /> <property name="mapperLocations"> <list> <value>classpath*:com/test/**/*Mapper.xml</value> </list> </property> <property name="configLocation" value="classpath:provider-sql-map-config.xml" /> </bean>
添加动态数据源管理类
DynamicDataSource类继承AbstractRoutingDataSource,实现determineCurrentLookupKey()方法。
package com.test.util; import com.test.util.CustomerContextHolder; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 动态数据源管理类 * */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return CustomerContextHolder.getCustomerType(); } }
数据源切换类
CustomerContextHolder类可以实现数据源A和B的切换,数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
package com.test.util; /** * 数据源切换类 * */ public class CustomerContextHolder { public static final String DATA_SOURCE_DEFAULT = "dataSourceTargetA"; public static final String DATA_SOURCE_B = "dataSourceTargetB"; private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static void setCustomerType(String customerType) { contextHolder.set(customerType); } public static String getCustomerType() { return contextHolder.get(); } public static void clearCustomerType() { contextHolder.remove(); } }
使用多数据源代码
下面是Controller层代码,需要查询B数据源,查询完成后切换为默认数据源A,具体代码如下:
/** * 订单列表查询,查询B数据源 * * @param request * @return */ @RequestMapping(value = "/list") public String list(HttpServletRequest request,Integer cId) { //设置数据源B CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_B); List<Order> orderList = salaryBillService.getSalaryOrderByOrgId(cId); request.setAttribute("orderList",orderList); //切换回默认数据源A CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_DEFAULT); return "/jsp/order"; }
上述四步操作完成后即可实现SpringMVC+Mybatis配置多个数据源并切换,有问题欢迎留言沟通哦!