随着业务变迁/需求变更,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 项目数据源 ?的更多相关文章

  1. Jar包方式运行web项目

    使用Maven进行打包 在自己的电脑终端中进入到pom.xml文件的目录中执行maven打包.命令为: mvn clean package 1 成功的标志为​上面显示BUILD SUCCESS成功打包 ...

  2. 在基于MVC的Web项目中使用Web API和直接连接两种方式混合式接入

    在我之前介绍的混合式开发框架中,其界面是基于Winform的实现方式,后台使用Web API.WCF服务以及直接连接数据库的几种方式混合式接入,在Web项目中我们也可以采用这种方式实现混合式的接入方式 ...

  3. 用idea简单创建web项目——两种方式

    最近同学让我教她们用idea创建web项目,于是我用两种方式创建web项目,并整理截图给她们看,一种是用maven创建,一种是不用maven创建,适合菜鸟哈哈~ 方法一:不用maven 1.解压tom ...

  4. 代码首要的目标应该是“解决问题”(包括“没有 bug”),其次的目标才是“简单优雅”。

    什么是现实理想主义者 曾经有人看了我的文章,以为我是一个“理想主义者”,来找我聊天.他说:“你知道吗,我跟你一样喜欢简单优雅的代码.上次我在某公司工作,看到他们的代码乱得不成样子,二话没说给他们重写了 ...

  5. iOS APP 如何做才安全

    本来 写了一篇<iOS 如何做才安全--逆向工程 - Reveal.IDA.Hopper.https抓包 等>,发现文章有点杂,并且“iOS 如何做才安全”这部分写的越来越多,觉得 分出来 ...

  6. 【转】iOS-APP如何做才安全

    iOS应用的安全性 常常被大家忽视. iOS 如何做才安全: 1.首先,我们可以通过iTunes 下载 AppStore的ipa文件(苹果 把开发者上传的ipa包 进行了加壳再放到AppStore中) ...

  7. SpringBoot项目整合Retrofit最佳实践,这才是最优雅的HTTP客户端工具!

    大家都知道okhttp是一款由square公司开源的java版本http客户端工具.实际上,square公司还开源了基于okhttp进一步封装的retrofit工具,用来支持通过接口的方式发起http ...

  8. 以正确的方式开源 Python 项目

    以正确的方式开源 Python 项目 大多数Python开发者至少都写过一个像工具.脚本.库或框架等对其他人也有用的工具.我写这篇文章的目的是让现有Python代码的开源过程尽可能清 晰和无痛.我不是 ...

  9. 三、自动化测试平台搭建-django-如何用mysql数据库做web项目

    从这节开始到后面说的大概内容如下: 这里说的是Django做一个web项目的大概框架,从下篇具体说Django中的模型(查询..),视图(请求,响应,cookie,session..),模板(验证码, ...

随机推荐

  1. laravel怎么创建一个简单的blog

    主要功能实现:点击标题跳转 第一步:创建路由: Route::get('/articles','ArticlesController@index'); Route::get('/articles/{i ...

  2. FastJson对于JSON格式字符串、JSON对象及JavaBean之间的相互转换

    fastJson对于json格式字符串的解析主要用到了一下三个类: JSON:fastJson的解析器,用于JSON格式字符串与JSON对象及javaBean之间的转换. JSONObject:fas ...

  3. MyEclipse 2014 破解补丁及激活步骤

    针对 MyEclipse Trail Expired 问题的解决方法 我用网上找的注册类生成注册码的方式注册了一下,重启MyEclipse仍然会有提示弹窗,不过剩余时间由 4 days变成了 5 da ...

  4. Windows PowerShell 默认颜色

    屏幕背景:1,36,86 屏幕文字:238,237,240 弹出文字:0,128,128 弹出窗口背景:255,255,255

  5. 闭包JS

    一句话概括的话:闭包就是一个函数,捕获作用域内的外部绑定. 官方的定义:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因为这些变量也称为该表达式的一部分. 自由变量和闭包的关系:自 ...

  6. 关于Net开发中一些SQLServer性能优化的建议

    一. ExecuteNonQuery和ExecuteScalar 对数据的更新不需要返回结果集,建议使用ExecuteNonQuery.由于不返回结果集可省掉网络数据传输.它仅仅返回受影响的行数.如果 ...

  7. js-txt文本处理

    js-txt文本处理 写自己主页项目时所产生的小问题拿出来给大家分享分享,以此共勉. ---DanlV TextArea的换行符处理 TextArea文本转换为Html:写入数据库时使用 js获取了t ...

  8. 谈谈webpack 的优势

    其优势主要可以归类为如下几个: 1. webpack 是以 commonJS 的形式来书写脚本滴,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移. 2. 能被模块化的不仅仅是 JS 了. ...

  9. 8位基本定时器(TIM4)

    简介:该定时器由一个带可编程预分频器的8位自动重载的向上计数器所组成,它可以用来作为时基发生器,具有溢出中断功能. 主要功能: (1)8位向上计数的自动重载计数器: (2)3位可编程的预分配器(可在运 ...

  10. spring boot controller路由 url 扫描不到问题

    spring boot项目出现controller的路由没被注册,原因:启动类application跟controller不在一个包中,扫描不到controller, 如启动类在com.oyx.a,c ...