怎样做才是最优雅方式切换 web 项目数据源 ?
随着业务变迁/需求变更,JavaEE 应用中会被迫连接多个数据源进行业务处理。
怎样在不影响原有项目结构的情况下,已最优雅/最简洁的方式动态切换数据源呢?
本文已一次添加数据源后动态切换实践为例,描述整个思考和实践过程,文中如有纰漏,还望指正。
1. 依赖 Spring 动态数据源实现

Spring 中提供了一个叫做 AbstractRoutingDataSource (抽象路由数据源)继承自 AbstractDataSource 并实现了 JDK DataSource 接口。
也就意味着继承 AbstractRoutingDataSource 并重写它 determineCurrentLookupKey 方法的类可以作为数据源,并个性化多数据源动态路由切换。
(如果你平时够仔细的话,现开源的数据库连接池都实现 DataSource 接口并进行了自己的个性化封装。)
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
对于一次 web 请求来说可以理解为单独的线程,将当前数据源暂存在线程当中是比较合理的做法。
public class DbContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
/**
* 设置数据源
*
* @param dbSourceEnum 要设置的数据库枚举名称
*/
public static void setDbType(DBSourceEnum dbSourceEnum) {
contextHolder.set(dbSourceEnum.getValue());
}
/**
* 取得当前数据源
*/
public static String getDbType() {
return String.valueOf(contextHolder.get());
}
/**
* 清除上下文数据
*/
public static void clearDbType() {
contextHolder.remove();
}
}
当然为了后期的扩展和维护,以及使用的便捷性,这里数据源对象我们引入枚举类型。
这样后续其他同事编程使用枚举,改动起来也相当方便,还能进行数据源的一些自定义说明。
public enum DBSourceEnum {
one("dataSource1"),
two("dataSource2");
private String value;
DBSourceEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
上述的 dataSource1/dataSource2 即为 spring-context 中已加载的数据源对象 Id。
<bean name="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
......
</bean>
<bean name="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
......
</bean>
接下来在 context 中配置继承自 AbstractRoutingDataSource 的 DynamicDataSource。
<bean id="dataSource" class="com.rambo.spm.core.multidb.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSource1" value-ref="dataSource1"/>
<entry key="dataSource2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean>
Ok,这样在配置后续 dao 层时使用该 DynamicDataSource 即可。
2. 最优雅的切换数据源方式
完成上述工作之后,其实动态切换数据源已经实现,在业务层如下面这样编程。
DbContextHolder.setDbType(DBSourceEnum.one);
List<Menu> menuList = menuService.selectList(null); DbContextHolder.setDbType(DBSourceEnum.two);
List<User> userList = userService.selectList(null);
缺点很明显,连接数据源2时要进行切换/不利于扩展/切换不当时给同事埋雷的几率很大。
和团队进行交流时,讨论出用强大 aop 来拦截 dao 层对象,动态切换数据源的方案。

对于 dao 层对象来说访问数据库的哪张表是确定的,编写自定义注解与 dao 层对象进行绑定。
自定义数据源注解如下:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DataSource { DBSourceEnum value() default DBSourceEnum.one;
}
编写切面处理对象,在 dao 层对象使用前进行拦截,顺手切换数据源,如果没有数据源注解,设置为默认。
所以对于项目中原配数据源 dao 层对象,不需要进行任何修改,切面处理如下。
@Before("cut()")
public void doBefore(JoinPoint joinPoint) {
DataSource dataSource = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
DbContextHolder.setDbType(dataSource != null ? dataSource.value() : DBSourceEnum.one);
log.info("当前数据源为:" + DbContextHolder.getDbType());
}
多数项目中 dao 层错综复杂的抽象和继承关系会给你 aop 切面拦截造成一定的困难,多思考、多实践总会有办法的。
好了,就这样吧,有没有感觉 aop 拦截方式比在程序中硬编码更容易扩展、更容易编程、更容易理解,当然也更优雅。
由此可扩展数据库方面很多,比如读写分离/分库分区....具体场景具体分析吧。
代码已托管在:https://git.oschina.net/LanboEx/spmvc-mybatis.git 有兴趣的朋友,可以检到本地 run 一下。
怎样做才是最优雅方式切换 web 项目数据源 ?的更多相关文章
- Jar包方式运行web项目
使用Maven进行打包 在自己的电脑终端中进入到pom.xml文件的目录中执行maven打包.命令为: mvn clean package 1 成功的标志为上面显示BUILD SUCCESS成功打包 ...
- 在基于MVC的Web项目中使用Web API和直接连接两种方式混合式接入
在我之前介绍的混合式开发框架中,其界面是基于Winform的实现方式,后台使用Web API.WCF服务以及直接连接数据库的几种方式混合式接入,在Web项目中我们也可以采用这种方式实现混合式的接入方式 ...
- 用idea简单创建web项目——两种方式
最近同学让我教她们用idea创建web项目,于是我用两种方式创建web项目,并整理截图给她们看,一种是用maven创建,一种是不用maven创建,适合菜鸟哈哈~ 方法一:不用maven 1.解压tom ...
- 代码首要的目标应该是“解决问题”(包括“没有 bug”),其次的目标才是“简单优雅”。
什么是现实理想主义者 曾经有人看了我的文章,以为我是一个“理想主义者”,来找我聊天.他说:“你知道吗,我跟你一样喜欢简单优雅的代码.上次我在某公司工作,看到他们的代码乱得不成样子,二话没说给他们重写了 ...
- iOS APP 如何做才安全
本来 写了一篇<iOS 如何做才安全--逆向工程 - Reveal.IDA.Hopper.https抓包 等>,发现文章有点杂,并且“iOS 如何做才安全”这部分写的越来越多,觉得 分出来 ...
- 【转】iOS-APP如何做才安全
iOS应用的安全性 常常被大家忽视. iOS 如何做才安全: 1.首先,我们可以通过iTunes 下载 AppStore的ipa文件(苹果 把开发者上传的ipa包 进行了加壳再放到AppStore中) ...
- SpringBoot项目整合Retrofit最佳实践,这才是最优雅的HTTP客户端工具!
大家都知道okhttp是一款由square公司开源的java版本http客户端工具.实际上,square公司还开源了基于okhttp进一步封装的retrofit工具,用来支持通过接口的方式发起http ...
- 以正确的方式开源 Python 项目
以正确的方式开源 Python 项目 大多数Python开发者至少都写过一个像工具.脚本.库或框架等对其他人也有用的工具.我写这篇文章的目的是让现有Python代码的开源过程尽可能清 晰和无痛.我不是 ...
- 三、自动化测试平台搭建-django-如何用mysql数据库做web项目
从这节开始到后面说的大概内容如下: 这里说的是Django做一个web项目的大概框架,从下篇具体说Django中的模型(查询..),视图(请求,响应,cookie,session..),模板(验证码, ...
随机推荐
- java 中的重载与重写 抽象类与接口的区别
. 重载与重写的区别: 重载(overload) | 重写(override) 1 方法的名称相同,参数个数.类型不同 | 方法名称.参数列表.返回值类型与父类完全相同 2 ...
- 利用workbench将excel数据导入到MySQL中
数据导入的方式(csv,txt之类) 在MySQL中,数据导入的方式有两种方式 通过第三方客户端导入(workbench) 通过mysql client 方式导入 通过mysql clinet的导入方 ...
- Python爬虫从入门到放弃(十四)之 Scrapy框架中选择器的用法
Scrapy提取数据有自己的一套机制,被称作选择器(selectors),通过特定的Xpath或者CSS表达式来选择HTML文件的某个部分Xpath是专门在XML文件中选择节点的语言,也可以用在HTM ...
- Java开发中碰到的Map的坑
这属于我在开发中碰过的坑 ,容器中存放者对象,当clear()的时候,出现的奇葩问题.好了,直接看代码: package com.DataType.yinyong; import java.util. ...
- 在Jekyll博客添加评论系统:gitment篇
最近在Github Pages上使用Jekyll搭建了个人博客( jacobpan3g.github.io/cn ), 当需要添加评论系统时,找了一下国内的几个第三方评论系统,如"多说&qu ...
- 如何生成Azure SAS Token
在Azure PaaS服务密钥的安全性文章中,提到过客户端实际上发送的是Token,而不是密钥.那么Token是该如何生成呢? Azure相应服务的SDK其实都提供了或者内置了生成Token的方法,可 ...
- 基于 WebRTC 技术的实时通信服务开发实践
随着直播的发展,直播实时互动性变得日益重要.又拍云在 WebRTC 的基础上,凭借多年的开发经验,结合当下实际情况,开发 UPRTC 系统,解决了网络延时.并发量大.客户端解码能力差等问题. WebR ...
- openjudge8465:马走日 [搜索]
描述 马在中国象棋以日字形规则移动. 请编写一段程序,给定n*m大小的棋盘,以及马的初始位置(x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点. 输入 第一行为整数T ...
- 树状数组(瞎bb) [树状数组]
Copyright:http://www.cnblogs.com/ZYBGMZL/ 树状数组是一个利用一维数组和位运算组成的求解区间问题的高效数据结构,其构造如图所示 首先,我们要用它解决单点修改.区 ...
- SSH三大框架的各自工作流程
一.Struts2的工作流程:1.用户在客户端发起请求,客户端会初始化一个servlet容器请求:2.servlet容器把请求会传递给context容器,context容器找到目标web工程.3.进行 ...