zoukankan      html  css  js  c++  java
  • 怎样做才是最优雅方式切换 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 一下。

      

  • 相关阅读:
    绿色版Notepad++ 加右键带图标菜单
    C#中string和StringBuilder的区别
    C#中string和String的区别
    C#中is和as的区别
    C#中抽象类(abstract)和接口(interface)的相同点与区别
    c++串口通信实例
    vs2017常用快捷键
    Qt编译opencv找不到头文件
    Qt常用快捷键
    二维数组和指针
  • 原文地址:https://www.cnblogs.com/java-class/p/7374623.html
Copyright © 2011-2022 走看看