亲手教你SpringBoot中的多数据源集成问题

目录
  • 引言
  • 应用场景
  • 集成方案

引言

其实对于分库分表这块的场景,目前市场上有很多成熟的开源中间件,eg:MyCAT,Cobar,sharding-JDBC等。

本文主要是介绍基于springboot的多数据源切换,轻量级的一种集成方案,对于小型的应用可以采用这种方案,我之前在项目中用到是因为简单,便于扩展以及优化。

应用场景

假设目前我们有以下几种数据访问的场景:
1.一个业务逻辑中对不同的库进行数据的操作(可能你们系统不存在这种场景,目前都时微服务的架构,每个微服务基本上是对应一个数据库比较多,其他的需要通过服务来方案。),
2.访问分库分表的场景;后面的文章会单独介绍下分库分表的集成。

假设这里,我们以6个库,每个库1000张表的例子来介绍),因为随着业务量的增长,一个库很难抗住这么大的访问量。比如说订单表,我们可以根据userid进行分库分表。
分库策略:userId%6[表的数量];
分库分表策略:库路由[userId/(6*1000)/1000],表路由[userId/(6*1000)%1000].

集成方案

方案总览:
方案是基于springjdbc中提供的api实现,看下下面两段代码,是我们的切入点,我们都是围绕着这2个核心方法进行集成的。

第一段代码是注册逻辑,会将defaultTargetDataSource和targetDataSources这两个数据源对象注册到resolvedDataSources中。

第二段是具体切换逻辑: 如果数据源为空,那么他就会找我们的默认数据源defaultTargetDataSource,如果设置了数据源,那么他就去读这个值Object lookupKey = determineCurrentLookupKey();我们后面会重写这个方法。

我们会在配置文件中配置主数据源(默认数据源)和其他数据源,然后在应用启动的时候注册到spring容器中,分别给defaultTargetDataSource和targetDataSources进行赋值,进而进行Bean注册。

真正的使用过程中,我们定义注解,通过切面,定位到当前线程是由访问哪个数据源(维护着一个ThreadLocal的对象),进而调用determineCurrentLookupKey方法,进行数据源的切换。

public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        }
        this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
        for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
            Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
            DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
            this.resolvedDataSources.put(lookupKey, dataSource);
        if (this.defaultTargetDataSource != null) {
            this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
    }

@Override
public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        return dataSource;

1.动态数据源注册注册默认数据源以及其他额外的数据源; 这里只是复制了核心的几个方法,具体的大家下载git看代码。

// 创建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DyncRouteDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        //默认数据源
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        //其他数据源
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        //注册到spring bean容器中
        registry.registerBeanDefinition("dataSource", beanDefinition);

2.在Application中加载动态数据源,这样spring容器启动的时候就会将数据源加载到内存中。

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MybatisAutoConfiguration.class })
@Import(DyncDataSourceRegister.class)
@ServletComponentScan
@ComponentScan(basePackages = { "com.happy.springboot.api","com.happy.springboot.service"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.数据源切换核心逻辑:创建一个数据源切换的注解,并且基于该注解实现切面逻辑,这里我们通过threadLocal来实现线程间的数据源切换;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
    // 是否分库
    boolean isSharding() default false;
    // 获取分库策略
    String strategy() default "";
}
// 动态数据源上下文
public class DyncDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static List<String> dataSourceNames = new ArrayList<String>();
    public static void setDataSource(String dataSourceName) {
        contextHolder.set(dataSourceName);
    }
    public static String getDataSource() {
        return contextHolder.get();
    public static void clearDataSource() {
        contextHolder.remove();
    public static boolean containsDataSource(String dataSourceName) {
        return dataSourceNames.contains(dataSourceName);
/**
 *
 * @author jasoHsu
 * 动态数据源在切换,将数据源设置到ThreadLocal对象中
 */
@Component
@Order(-10)
@Aspect
public class DyncDataSourceInterceptor {
    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Exception {
        String dbIndx = null;
        String targetDataSourceName = targetDataSource.value() + (dbIndx == null ? "" : dbIndx);
        if (DyncDataSourceContextHolder.containsDataSource(targetDataSourceName)) {
            DyncDataSourceContextHolder.setDataSource(targetDataSourceName);
        }
    @After("@annotation(targetDataSource)")
    public void resetDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        DyncDataSourceContextHolder.clearDataSource();
//
public class DyncRouteDataSource extends AbstractRoutingDataSource {
@Override
    protected Object determineCurrentLookupKey() {
        return DyncDataSourceContextHolder.getDataSource();
    public DataSource findTargetDataSource() {
        return this.determineTargetDataSource();

4.与mybatis集成,其实这一步与前面的基本一致。
只是将在sessionFactory以及数据管理器中,将动态数据源注册进去【dynamicRouteDataSource】,而非之前的静态数据源【getMasterDataSource()】

@Resource
    DyncRouteDataSource dynamicRouteDataSource;

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory getinvestListingSqlSessionFactory() throws Exception {
        String configLocation = "classpath:/conf/mybatis/configuration.xml";
        String mapperLocation = "classpath*:/com/happy/springboot/service/dao/*/*Mapper.xml";
        SqlSessionFactory sqlSessionFactory = createDefaultSqlSessionFactory(dynamicRouteDataSource, configLocation,
                mapperLocation);
        return sqlSessionFactory;
    }
    @Bean(name = "txManager")
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dynamicRouteDataSource);

5.核心配置参数:

spring:
  dataSource:
    happyboot:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/happy_springboot?characterEncoding=utf8&useSSL=true
      username: root
      password: admin
    extdsnames: happyboot01,happyboot02
    happyboot01:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/happyboot01?characterEncoding=utf8&useSSL=true
      username: root
      password: admin
    happyboot02:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/happyboot02?characterEncoding=utf8&useSSL=true
      username: root
      password: admin

6.应用代码

@Service
public class UserExtServiceImpl implements IUserExtService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @TargetDataSource(value="happyboot01")
    @Override
    public UserInfo getUserBy(Long id) {
        return userInfoMapper.selectByPrimaryKey(id);
    }
}

到此这篇关于关于SpringBoot中的多数据源集成的文章就介绍到这了,更多相关SpringBoot多数据源集成内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot集成mybatisPlus+多数据源的实现示例

    该项目主要实现mybatisplus.多数据源.lombok.druid的集成 主要参考 https://mp.baomidou.com/guide/quick-start.html 项目地址:https://github.com/Blankwhiter/mybatisplus-springboot release1.0 项目结构: 一.创建表以及测试数据 CREATE TABLE user ( id VARCHAR(32) NOT NULL COMMENT '主键ID', name VARCH

  • SpringBoot集成多数据源解析

    一,前面我们介绍了springboot的快速启动,大家肯定对springboot也有所了解,下面我们来介绍一下springboot怎么集成多数据源. 在有的项目开发中需要在一个项目中访问多个数据源或者两个项目之间通信(实质上是互相访问对方的数据库),在这里,我们介绍一下在一个项目中如何集成多个数据源(即访问多个不同的数据库),因为在项目中有时会有这种需求,比如在一个大型项目开发中,一个数据库中保存数据的索引,各种使用频繁的数据,另一个数据库中保存其他的数据. 1.下面我们来讨论一个问题,怎么集成

  • springboot配置多数据源并集成Druid和mybatis的操作

    可以是mysql,oracle等多种不同数据源 项目结构 注意:只有@Primary的数据源所控制的mapper文件加注解@Mapper,否则mybatis无法切换扫描:即本文中的mapper/opener文件夹下mapper加注解 1.pom 驱动之外加入druid和mybatis等pom,整合mybatis自行搜索 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-b

  • 亲手教你SpringBoot中的多数据源集成问题

    目录 引言 应用场景 集成方案 引言 其实对于分库分表这块的场景,目前市场上有很多成熟的开源中间件,eg:MyCAT,Cobar,sharding-JDBC等. 本文主要是介绍基于springboot的多数据源切换,轻量级的一种集成方案,对于小型的应用可以采用这种方案,我之前在项目中用到是因为简单,便于扩展以及优化. 应用场景 假设目前我们有以下几种数据访问的场景:1.一个业务逻辑中对不同的库进行数据的操作(可能你们系统不存在这种场景,目前都时微服务的架构,每个微服务基本上是对应一个数据库比较多

  • springboot中mybatis多数据源动态切换实现

    目录 多数据源配置引入 动态数据源路由实现 动态数据源切换使用 案例源码 在开发中,动态数据源配置还是用的比较多的,比如在多数据源使用方面,又或者是在多个DB之间切换方面.这里给出一个动态数据源的配置方案,两个DB均以mysql为例. 多数据源配置引入 mybatis和mysql在springboot中的引入这里就不在说了,不了解的可以参见springboot中mysql与mybatis的引入. 数据源配置如下: datasource: master: type: com.alibaba.dru

  • 详细聊聊SpringBoot中动态切换数据源的方法

    其实这个表示有点不太对,应该是 Druid 动态切换数据源的方法,只是应用在了 springboot 框架中,准备代码准备了半天,之前在一次数据库迁移中使用了,发现 Druid 还是很强大的,用来做动态数据源切换很方便. 首先这里的场景跟我原来用的有点点区别,在项目中使用的是通过配置中心控制数据源切换,统一切换,而这里的例子多加了个可以根据接口注解配置 第一部分是最核心的,如何基于 Spring JDBC 和 Druid 来实现数据源切换,是继承了org.springframework.jdbc

  • 图文详解SpringBoot中Log日志的集成

    目录 一.日志的分类 1.名字分类 2.日志分类 3.日志级别 二.logback简介 1. 项目中日志分类 2. java项目中使用 三.SpringBoot中日志使用 1.将Springboot的日志默认级别改为debug 2.SpringBoot项目启动就会有一个默认的日志对象:logger 四.idea中使用日志插件简化开发 1.下载Log Support2插件 2.重启idea 3.Settings里面会新增一个Log Support目录 4.如果直接使用Android的日志会冲突 5

  • 教你在SpringBoot中管理多环境配置文件

    实现 1.可以通过配置项 spring.profiles.active 的值来激活对应的环境(思路:使用一个默认的文件作为通用配置文件,不同的配置项写入不同环境的配置文件中,部署不同环境时,只需要修改spring.profiles.active的值即可.个人习惯在通用配置文件只保留spring.profiles.active一个配置项,灵活性高一点) 2.通过部署命令java -jar xxx.jar --spring.profiles=xxx 来激活指定的配置项 针对不同的环境,一般常用的命名

  • springboot 中整合mybatis多数据源不使用JPA

    目录 前言: 1 创建一个springboot项目 项目创建成功了,那么开始说下整合mybatis 上面的配置可以直接使用 如果上面的配置都完成的话那么我们来自动生成一下 在下图两个数据源完全生成好后的目录结构 前言: 小伙伴说能不能用springboot整合一下mybatis多数据源不使用JPA进行数据库连接操作.那么说干就干 1 创建一个springboot项目 流程就不介绍了,按图操作即可 创建项目后可能会发现,这个项目展示的有些不对劲啊,java类前面还有个小J号,这是由于maven包还

  • Prometheus 入门教程之SpringBoot 实现自定义指标监控

    上篇文章我们已经可以在 Grafana 上看到对应的 SpringBoot 应用信息了,通过这些信息我们可以对 SpringBoot 应用有更全面的监控.但是如果我们需要对一些业务指标做监控,我们应该怎么做呢?这篇文章就带你一步步实现一个模拟的订单业务指标监控. 假设我们有一个订单系统,我们需要监控它的实时订单总额.10 分钟内的下单失败率.请求失败数.那么我们应该怎么做呢? 添加业务监控指标 在 spring-web-prometheus-demo 项目的基础上,我们添加一个 Promethe

  • springboot下配置多数据源的方法

    一.springboot 简介 SpringBoot使开发独立的,产品级别的基于Spring的应用变得非常简单,你只需"just run". 我们为Spring平台及第三方库提 供开箱即用的设置,这样你就可以有条不紊地开始.多数Spring Boot应用需要很少的Spring配置. 你可以使用SpringBoot创建Java应用,并使用 java -jar 启动它或采用传统的war部署方式.我们也提供了一个运行"spring 脚本"的命令行工具. 二.传统的Dat

  • SpringBoot中发送QQ邮件功能的实现代码

    本文是vhr系列的第十二篇,项目地址 https://github.com/lenve/vhr 邮件发送也是一个老生常谈的问题了,代码虽然简单,但是许多小伙伴对过程不太理解,所以还是打算和各位小伙伴聊聊这个话题. 邮件协议 我们经常会听到各种各样的邮件协议,比如SMTP.POP3.IMAP,那么这些协议有什么作用,有什么区别?我们先来讨论一下这个问题. SMTP是一个基于TCP/IP的应用层协议,江湖地位有点类似于HTTP,SMTP服务器默认监听的端口号为25.看到这里,小伙伴们可能会想到既然S

  • 手把手教你SpringBoot过滤器N种注册方式

    要说在 Spring Boot 中注册过滤器有三种方式,你都能想到哪些呢?今天松哥就来和大家聊一聊 Spring Boot 中注册过滤器的三种方式! 其实本来是想和大家聊 Spring Security 过滤器链的问题的,结果看源码看着看着就跑题了,索性就先和大家聊一聊 Spring Boot 中注册过滤器的三种方式,算是给 后面的 Spring Security 打一点基础. 1.@WebFilter 通过 @WebFilter 注解来标记一个过滤器,这种方式相信大家很容易想到.这是将 Ser

随机推荐