Spring 多数据源动态切换
最近我们项目组里碰到个问题,我们系统跟其他系统业务上耦合度很高,但是又没法剥离,其他业务系统要更换达梦数据库,因为业务上跟该系统交互比较多,所以我们要提前准备,待该系统完成数据库切换后,我们必须同步完成切换,所以就要求添加第二个数据源。
使用框架
目前开发用的是 Spring MVC、Mybatis、Shiro
第一步,增加数据源配置
############ 共用配置 ############
JDBC_DATABASE_INITIALSIZE=10
JDBC_DATABASE_MAXACTIVE=2500
JDBC_DATABASE_MINIDLE=10
JDBC_DATABASE_DEFAULT_AUTO_COMMIT=true
JDBC_DATABASE_VALIDATIONQUERY=SELECT 'x' from dual############ 主数据库配置 DATA_BASE 开始 ############
M_JDBC_DATABASE_DRIVER_CLASS=oracle.jdbc.driver.OracleDriver
M_JDBC_DATABASE_URI=jdbc:oracle:thin:@10.236.0.56:1523/ICTUAT
M_JDBC_DATABASE_USERNAME=ams
M_JDBC_DATABASE_PWD=ENC(3mFCWDzzxdS4WRKz4C/FI9splF4ZBFlkXAVPekfxsWA=)############ 数据库1配置 DATA_BASE 开始 ############
S_JDBC_DATABASE_DRIVER_CLASS=oracle.jdbc.driver.OracleDriver
S_JDBC_DATABASE_URI=jdbc:oracle:thin:@10.168.38.90:10530/ebs_FINTEST3
S_JDBC_DATABASE_USERNAME=apps
S_JDBC_DATABASE_PWD=ENC(silkLqN1Z9mH1cs/sDmdDJQI5kcRg3Kf)
第二步,配置数据源
<!-- 主数据源 --><bean id="amsDataSource" class="com.alibaba.druid.pool.DruidDataSource"init-method="init" destroy-method="close"><!--用来连接数据库的url --><property name="url" value="${M_JDBC_DATABASE_URI}" /><!--用来连接数据库的用户名 --><property name="username" value="${M_JDBC_DATABASE_USERNAME}" /><!-- 用来链接数据的密码 --><property name="password" value="${M_JDBC_DATABASE_PWD}" /><!-- 初始化连接大小 --><property name="initialSize" value="${JDBC_DATABASE_INITIALSIZE}" /><!-- 连接池最大使用连接数量 --><property name="maxActive" value="${JDBC_DATABASE_MAXACTIVE}" /><!-- 连接池最小空闲 --><property name="minIdle" value="${JDBC_DATABASE_MINIDLE}" /><!-- 获取连接最大等待时间 --><property name="maxWait" value="9000" /><!-- <property name="poolPreparedStatements" value="true" />--><!--<property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> --><!-- 用来检测连接是否有效的sql --><property name="validationQuery" value="${JDBC_DATABASE_VALIDATIONQUERY}" /><!--属性validationQuery配置为非null时,以下三个属性有效 --><!--申请连接时执行validationQuery检测连接是否有效--><property name="testOnBorrow" value="false" /><!--归还连接时执行validationQuery检测连接是否有效--><property name="testOnReturn" value="false" /><!--如果空闲时间大于timeBetweenEvictionRunsMillis,申请连接的时执行validationQuery检测--><property name="testWhileIdle" value="true" /><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --><property name="timeBetweenEvictionRunsMillis" value="60000" /><!-- 打开removeAbandoned功能 --><property name="removeAbandoned" value="false" /><!-- 3600秒,也就是60分钟 --><property name="removeAbandonedTimeout" value="21600" /><!-- 关闭abanded连接时输出错误日志 --><property name="logAbandoned" value="true" /><!-- 监控数据库 --><property name="filters" value="mergeStat,log4j" /></bean><!-- 数据源1--><bean id="erpDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!--用来连接数据库的url --><property name="url" value="${S_JDBC_DATABASE_URI}" /><!--用来连接数据库的用户名 --><property name="username" value="${S_JDBC_DATABASE_USERNAME}" /><!-- 用来链接数据的密码 --><property name="password" value="${S_JDBC_DATABASE_PWD}" /><!-- 初始化连接大小 --><property name="initialSize" value="${JDBC_DATABASE_INITIALSIZE}" /><!-- 连接池最大使用连接数量 --><property name="maxActive" value="${JDBC_DATABASE_MAXACTIVE}" /><!-- 连接池最小空闲 --><property name="minIdle" value="${JDBC_DATABASE_MINIDLE}" /><!-- 获取连接最大等待时间 --><property name="maxWait" value="9000" /><!-- <property name="poolPreparedStatements" value="true" />--><!--<property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> --><!-- 用来检测连接是否有效的sql --><property name="validationQuery" value="${JDBC_DATABASE_VALIDATIONQUERY}" /><!--属性validationQuery配置为非null时,以下三个属性有效 --><!--申请连接时执行validationQuery检测连接是否有效--><property name="testOnBorrow" value="false" /><!--归还连接时执行validationQuery检测连接是否有效--><property name="testOnReturn" value="false" /><!--如果空闲时间大于timeBetweenEvictionRunsMillis,申请连接的时执行validationQuery检测--><property name="testWhileIdle" value="true" /><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --><property name="timeBetweenEvictionRunsMillis" value="60000" /><!-- 打开removeAbandoned功能 --><property name="removeAbandoned" value="false" /><!-- 3600秒,也就是60分钟 --><property name="removeAbandonedTimeout" value="21600" /><!-- 关闭abanded连接时输出错误日志 --><property name="logAbandoned" value="true" /><!-- 监控数据库 --><property name="filters" value="mergeStat,log4j" /></bean>
第三步,配置DataSourceTransactionManager
<!-- 事务管理器--><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" /></bean>
第四步,配置 DataSource
<bean id="dataSource" class="cn.chinaunicom.sdsi.frm.spring.datasource.route.DynamicDataSourceRoute"><!-- 默认数据源--><property name="defaultTargetDataSource" ref="amsDataSource" /><property name="targetDataSources"><map key-type="java.lang.String"><entry key="master" value-ref="amsDataSource" /><entry key="second" value-ref="erpDataSource" /></map></property></bean>
第五步,控制Spring切换DataSource
package cn.chinaunicom.sdsi.frm.spring.datasource.route;import cn.chinaunicom.sdsi.frm.spring.datasource.config.DynamicDataSourceConfig;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** @Description 数据源路由* @Author pgq* @DATE 2024/9/12 15:34* @Version 1.0*/
public class DynamicDataSourceRoute extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceConfig.getDataSource();}
}
第六步,通过ThreadLocal 切换数据源
package cn.chinaunicom.sdsi.frm.spring.datasource.config;import java.util.Objects;/*** @Description 多数据源配置类* @Author pgq* @DATE 2024/9/12 14:49* @Version 1.0*/
public class DynamicDataSourceConfig {private static final ThreadLocal<String> dataSources = new ThreadLocal<>();/*** 设置数据源类型** @param dataSourceType*/public static void setDataSource(String dataSourceType) {dataSources.set(dataSourceType);}/*** 获取数据源类型** @return*/public static String getDataSource() {String dataSource = "";try {dataSource = dataSources.get();} finally {if (!Objects.isNull(dataSource)) {dataSources.remove();}}return dataSource;}}
第七步,代码使用
说明:目前分位 4 层,控制层、业务逻辑层、服务层、数据处理层,数据源的切换在business层,因为 Service 层有==@Transactional 注解==(使用该注解时,Spring 会获取默认的数据源,所以我们必须在 Service 层的上一层完成数据源的切换,业务逻辑又不能写在层,所以添加了 business 层,Service 层保证单个数据源的异常回滚就可以了)。