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

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

人已赞赏
随笔日记

Kubernetes 笔记 11 Pod 扩容与缩容 双十一前后的忙碌

2020-11-9 4:04:01

随笔日记

使用 ASP.NET Core MVC 创建 Web API(一)

2020-11-9 4:04:03

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索