zoukankan      html  css  js  c++  java
  • SpringMVC+Mybatis 如何配置多个数据源并切换?

    最近公司一个项目需要连接两个数据库(A和B)操作,有的模块查询A库,有的模块查询B库,因此需要改造下,项目后台用的是SpringMVC+Mybatis+MySQL架构,折腾了两天后终于搞定了,在这里记录过改造过程。

    使用场景

    多数据源的使用的场景一般有:

    1. 主从数据库切换
    2. 读写分离
    3. 兼容旧库

    实现原理

    Spring2.x的版本中采用Proxy模式,就是在方案中实现一个虚拟的数据源,并且用它来封装数据源选择逻辑,这样就可以有效地将数据源选择逻辑从Client中分离出来。Client提供选择所需的上下文,由虚拟的DynamicDataSource根据Client提供的上下文来实现数据源的选择。

    具体的实现是虚拟的DynamicDataSource仅需继承AbstractRoutingDataSource实现determineCurrentLookupKey(),该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到则用默认的resolvedDefaultDataSource。

    详细实现过程

    1. 修改spring的配置文件
      配置文件里需要配置多个数据源,改造后主要配置如下:    
    <bean id="dataSourceTargetA" class="com.mchange.v2.c3p0.ComboPooledDataSource"
            destroy-method="close" scope="singleton">
            <property name="driverClass" value="${jdbc.a.driverClassName}" />
            <property name="jdbcUrl" value="${jdbc.a.url}" />
            <property name="user" value="${jdbc.a.username}" />
            <property name="password" value="${jdbc.a.password}" />
            <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
            <property name="acquireIncrement" value="${c3p0.acquireIncrement}"></property>
            <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
            <property name="initialPoolSize" value="${c3p0.initialPoolSize}"></property>
            <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
            <property name="maxIdleTime" value="${c3p0.maxIdleTime}"></property>
            <!--连接池中保留的最大连接数。Default: 15 -->
            <property name="maxPoolSize" value="${c3p0.maxPoolSize}"></property>
            <property name="minPoolSize" value="${c3p0.minPoolSize}"></property>
            <!--两次连接中间隔时间,单位毫秒。Default: 1000 -->
            <property name="acquireRetryDelay" value="1000"></property>
            <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
            <property name="acquireRetryAttempts" value="60"></property>
            <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default:
                false -->
            <property name="breakAfterAcquireFailure" value="false"></property>
        </bean>
    
        <bean id="dataSourceTargetB" class="com.mchange.v2.c3p0.ComboPooledDataSource"
              destroy-method="close" scope="singleton">
            <property name="driverClass" value="${jdbc.b.driverClassName}" />
            <property name="jdbcUrl" value="${jdbc.b.url}" />
            <property name="user" value="${jdbc.b.username}" />
            <property name="password" value="${jdbc.b.password}" />
            <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
            <property name="acquireIncrement" value="${c3p0.acquireIncrement}"></property>
            <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
            <property name="initialPoolSize" value="${c3p0.initialPoolSize}"></property>
            <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
            <property name="maxIdleTime" value="${c3p0.maxIdleTime}"></property>
            <!--连接池中保留的最大连接数。Default: 15 -->
            <property name="maxPoolSize" value="${c3p0.maxPoolSize}"></property>
            <property name="minPoolSize" value="${c3p0.minPoolSize}"></property>
            <!--两次连接中间隔时间,单位毫秒。Default: 1000 -->
            <property name="acquireRetryDelay" value="1000"></property>
            <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
            <property name="acquireRetryAttempts" value="60"></property>
            <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default:
                false -->
            <property name="breakAfterAcquireFailure" value="false"></property>
        </bean>
        <!-- 动态数据源 -->
        <bean id="dynamicDataSource" class="com.test.util.DynamicDataSource" >
            <property name="targetDataSources">
                <map key-type="java.lang.String">
                    <entry value-ref="dataSourceTargetA" key="dataSourceTargetA"></entry>
                    <entry value-ref="dataSourceTargetB" key="dataSourceTargetB"></entry>
                </map>
            </property>
            <property name="defaultTargetDataSource" ref="dataSourceTargetA" >
            </property>
        </bean>
    
    
        <bean id="txManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dynamicDataSource" />
        </bean>
    
    
        <!-- MyBatis ORM operation class -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dynamicDataSource" />
            <property name="mapperLocations">
                <list>
                    <value>classpath*:com/test/**/*Mapper.xml</value>
                </list>
            </property>
            <property name="configLocation" value="classpath:provider-sql-map-config.xml" />
        </bean>

      

      2.添加动态数据源管理类

    DynamicDataSource类继承AbstractRoutingDataSource,实现determineCurrentLookupKey()方法。

    package com.test.util;
    import com.test.util.CustomerContextHolder;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * 动态数据源管理类
     *
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return CustomerContextHolder.getCustomerType();
        }
    }

      3. 数据源切换类

    CustomerContextHolder类可以实现数据源A和B的切换,数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰。

    package com.test.util;
    /**
     * 数据源切换类
     *
     */
    public class CustomerContextHolder {
        public static final String DATA_SOURCE_DEFAULT = "dataSourceTargetA";
        public static final String DATA_SOURCE_B = "dataSourceTargetB";
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
        public static void setCustomerType(String customerType) {
            contextHolder.set(customerType);
        }
        public static String getCustomerType() {
            return contextHolder.get();
        }
        public static void clearCustomerType() {
            contextHolder.remove();
        }
    }

     4. 使用多数据源代码

    下面是Controller层代码,需要查询B数据源,查询完成后切换为默认数据源A,具体代码如下:

    /**
         * 订单列表查询,查询B数据源
         *
         * @param request
         * @return
         */
        @RequestMapping(value = "/list")
        public String list(HttpServletRequest request, Integer cId) {
            //设置数据源B
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_B);
            List<Order> orderList = orderService.getListByCid(cId);
            request.setAttribute("orderList", orderList);
            //切换回默认数据源A
            CustomerContextHolder.setCustomerType(CustomerContextHolder.DATA_SOURCE_DEFAULT);
            return "/jsp/order";
        }

    上述四步操作完成后即可实现SpringMVC+Mybatis配置多个数据源并切换,有问题欢迎留言沟通哦!

    推荐阅读

    1.Spring Boot 2.X 如何优雅的解决跨域问题?
    2.Redis Cluster搭建高可用Redis服务器集群
    3.为什么单线程的Redis这么快?
    4.Spring Boot集成spring session实现session共享
    5.Spring Boot入门-快速搭建web项目
    6.Spring Boot2.0整合Redis
    7.一篇文章搞定SpringMVC参数绑定

    限时领取免费Java相关资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo/Kafka、Hadoop、Hbase、Flink等高并发分布式、大数据、机器学习等技术。

    资料传送门:  https://mp.weixin.qq.com/s/u2b_NVNuMuAPE0w4lc45fw

    关注下方公众号即可免费领取:

    Java碎碎念公众号

  • 相关阅读:
    公有云、私有云与混合云到底有什么区别?
    分享两个“整人”的脚本语言代码
    最强搜书工具集合
    关于云服务器恢复快照时后服务器变得卡顿等问题的解决方法
    Centos8.0编译安装稳定最新版的nginx
    彻底禁止Win10自动更新工具Windows Update Blocker v1.5 汉化版
    阿里云轻量应用服务器回滚快照后造成卡顿问题的解决方法
    IaaS、PaaS和SaaS的区别
    Everything文件搜索工具
    Windows10自带软件一款性能监控工具
  • 原文地址:https://www.cnblogs.com/haha12/p/10613549.html
Copyright © 2011-2022 走看看