今天我想简单地分享一下如何将一个老项目从单数据源切换为多数据源的过程。这个项目是一个使用 WAR 部署的传统 JSP Web 项目,运行在 JDK 1.7 环境下,项目中并没有使用 Spring Boot,而仅仅采用了 Spring MVC 框架。我的主要任务是将原本使用单一数据源的架构,升级为支持多数据源的架构。
为此,首先需要梳理清楚当前项目的模块依赖和数据源的使用情况,了解项目中所有的模块和类是如何引用和交互的,特别是涉及到数据库操作的部分。
引用排查
我也很直接,直接找到datasource关键字直接全局搜索,找到了很多引用地方,有些确实是命名不是很规范,并不是数据源也起名叫了这个名字,第一步直接去除没有用的相关类,做一个简单的筛除。如图所示:
接下来,我将剩余的引用部分划分为三个主要部分,具体如下:第一部分是与XML配置相关的内容。由于该项目是一个较为传统的Spring MVC老项目,因此所有的Bean依赖关系都是在XML文件中显式配置的。这一部分的工作主要是分析和梳理XML配置文件中与Bean定义及依赖注入相关的内容。
第二部分是Java引用的相关内容。对于一些XML中配置好的Bean,这些配置会被注入到Java类的相应位置,并在运行时使用。因此,这一部分需要重点关注那些通过XML配置注入的Bean以及它们在Java代码中的应用场景。
最后第三部分是关于properties配置文件的检查。需要检查是否有单独的配置项存在于properties文件中,这些配置项可能会影响系统的某些行为或参数设置。
业务梳理
这部分不太好说,需要自己对整个项目有所掌握才可以,要不然会让自己看的头疼,这部分看的时候,大概想了一下为什么这里这么用,这里用到数据源做了哪些业务,如果切换成多数据源后,应该如何处理。
我大概看了一下有基本下面几种情况:
- 注入数据源,直接生成jdbctemplate对象后,在代码里写业务逻辑执行SQL,看的头疼~~
- 注入到sqlsessionfactorybean中,集成到mybatis中。
- 使用现成的spring-security,注入数据源后,直接查询各种权限信息。
- 国际化配置使用到了数据源信息。
目前就这几种,因为项目使用的是jndi的方式注入,所以对于多数据源来说也有一些困难。不过我的大概思路就是将数据源注入个默认数据源,使用动态key的方式切换数据源。
后期思路
比如,配置文件首先就需要有多个数据源的信息,如下所示:
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/db1"/><property name="username" value="user1"/><property name="password" value="pass1"/>
</bean><bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/db2"/><property name="username" value="user2"/><property name="password" value="pass2"/>
</bean><bean id="sqlSessionFactory" class="ReloadableSqlSessionFactoryBean"><property name="targetDataSources"><map key-type="java.lang.String"><entry key="dataSource1" value-ref="dataSource1"/><entry key="dataSource2" value-ref="dataSource2"/></map></property>
</bean>
定义数据源路由
public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceKey(String key) {contextHolder.set(key);}public static String getDataSourceKey() {return contextHolder.get();}public static void clearDataSourceKey() {contextHolder.remove();}
}
配置动态数据源
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceKey();}
}
集成到SqlSessionFactoryBean,在 ReloadableSqlSessionFactoryBean 中使用动态数据源:
public class MySqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean {private static final Log log = LogFactory.getLog(MySqlSessionFactoryBean.class);private AbstractRoutingDataSource routingDataSource;public void setTargetDataSources(Map<Object, Object> targetDataSources) {if (routingDataSource == null) {routingDataSource = new DynamicDataSource();}routingDataSource.setTargetDataSources(targetDataSources);routingDataSource.setDefaultTargetDataSource(targetDataSources.values().iterator().next());super.setDataSource(routingDataSource);}@Overrideprotected SqlSessionFactory buildSqlSessionFactory() throws Exception {return super.buildSqlSessionFactory();}@Overridepublic void destroy() throws Exception {// 清理资源}
}
在需要切换数据源的地方调用 DataSourceContextHolder.setDataSourceKey("dataSource1") 或 DataSourceContextHolder.setDataSourceKey("dataSource2")。
总的来说,将传统单数据源架构迁移到多数据源架构并不简单,但通过合理的模块梳理和逐步推进,整个过程可以得到有效实施。