随着业务变迁/需求变更,JavaEE 应用中会被迫连接多个数据源进行业务处理。 怎样在不影响原有项目结构的情况下,已最优雅/最简洁的方式动态切换数据源呢? 本文已一次添加数据源后动态切换实践为例,描述整个思考和实践过程,文中如有纰漏,还望指正。1. 依赖 ...
随着业务变迁/需求变更,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 项目数据源 ?
关键词:web
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们:
admin#shaoqun.com
(#换成@)。