Springboot实现多数据源整合的两种方式

原文地址 # Springboot实现多数据源整合的两种方式

🚐 一、前言

  • 大家好,我是小诚,不知不觉上一次更文已经是 20 多天前了!其实这段时间也一直没有闲着,一个是在梳理之前的文章知识和资源,用于搭建技术圈子,另外一个就是在思考自己的一个输出方向,社区发展得很迅速,热榜各种各类的文章都有,深思熟虑后,还是坚持文章在精不在多,质量标准更加重要,所以今后博文的方向会更加偏向实战和经验,争取分享更加有价值的博文!

  • 如果文章对你有帮助,可以帮忙一键三连和专栏订阅哦! 技术圈子经过这段时间的筹划,已经初步成型!有兴趣、志同道合的小伙伴可以查看左边导航栏的技术圈子介绍,期待你们的加入!

  • 本篇文章重点介绍 SpringBoot 集合 MyBatis 和 MyBatis-Plus 整合多数据源方面的知识!

🚅 二、专栏推荐

良心推荐: 下面的相关技术专栏还在免费分享哦,大家可以帮忙点点订阅哦!

JAVA 进阶知识大全

算法日记修行

🚔 三、整合多数据源需要了解的知识

1、何时会使用到多数据源

一个技术的出现、应用必然是为了解决存在的某些问题,多数据源出现常见的场景如下:

(1)、与第三方对接时,有些合作方并不会为了你的某些需求而给你开发一个功能,他们可以提供给你一个可以访问数据源的只读账号,你需要获取什么数据由你自己进行逻辑处理,这时候就避免不了需要进行多数据源整合了。

(2)、业务数据达到了一个量级,使用单一数据库存储达到了一个瓶颈,需要进行分库分表等操作进行数据管理,在操作数据时,不可避免的涉及到多数据源问题。

2、多数据源整合有哪些方式

参考了网上的许多材料,发现整合方式无外乎以下几种:

(1)、使用分包方式,不同的数据源配置不同的 MapperScan 和 mapper 文件

(2)、使用 AOP 切片方式,实现动态数据源切换 (如果对 Aop 不是很熟悉,欢迎查看我之前的一篇文章,这知识保熟哦!【什么是面向切面编程?】)

(3)、使用数据库代理中间件,如 Mycat 等

3、不同方式之间的区别

(1)、分包方式可以集合 JTA(JAVA Transactional API) 实现分布式事务,但是整个流程的实现相对来说比较复杂。

(2)、AOP 动态配置数据源方式缺点在于无法实现全局分布式事务,所以如果只是对接第三方数据源,不涉及到需要保证分布式事务的话,是可以作为一种选择。

(3)、使用数据库代理中间件方式是现在比较流行的一种方式,很多大厂也是使用这种方式 ,开发者不需要关注太多与业务无关的问题,把它们都交给数据库代理中间件去处理,大量的通用的数据聚合,事务,数据源切换都由中间件来处理,中间件的性能与处理能力将直接决定应用的读写性能,比较常见的有 Mycat、TDDL 等。现在阿里出了100%自研的分布式数据库OceanBase,从最底层支持分布式,性能也非常强大,大家感兴趣的可以去了解下!

4、本文实战选择的方式

鉴于本次遇到需求的整合多数据源的场景是需要 对接第三方的数据 ,暂不涉及到分布式事务问题 ,所以本文实战整合多数据源使用的方式是【分包方式】实现简单的多数据源整合,至于其他方式和分布式事务的坑,后面再慢慢填吧(o(╥﹏╥)o)!

🚢 四、SpringBoot+MyBatis 整合多数据源

🔴 4.1 说明

本次案例涉及到的代码比较多,因此文章只贴出部分,全部案例代码已经上传到 Gitee,需要者可直接访问:【SpringBoot 结合 MyBatis 整合多数据源】,项目结构如下:

🟠 4.2 涉及依赖包

  • spring-boot-starter-web -- web 相关支持
  • mybatis-spring-boot-starter -- springboot 整合 mybatis 依赖
  • mysql-connector-java -- mysql 数据驱动
  • lombok -- 自动生成实体类常用方法依赖包
  • hutool-all -- 常用方法封装依赖包
xml 复制代码
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.4.5</version>
        </dependency>

🟡 4.3 项目配置

yaml 复制代码
# 项目启动端口
server:
  port: 9090

# 项目 名称
spring:
  application:
    name: multi-datasource-instance
  datasource:
    # 主数据库
    master:
      # 注意,整合多数据源时如果使用springboot默认的数据库连接池Hikari,指定连接数据使用的是jdbc-url而不是url属性
      jdbc-url: jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    # 副数据库
    slave:
      # 注意,整合多数据源时如果使用springboot默认的数据库连接池Hikari,指定连接数据使用的是jdbc-url而不是url属性
      jdbc-url: jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver

🟢 4.4 编写主副数据库数据源配置

1、主数据源相关配置:主要是指定主数据源、扫描的 mapper 地址、事务管理器等信息。

less 复制代码
@Configuration
// 指定主数据库扫描对应的Mapper文件,生成代理对象
@MapperScan(basePackages ="com.diary.it.multi.datasource.mapper" ,sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {

    // mapper.xml所在地址
    private static final String MAPPER_LOCATION = "classpath*:mapper/*.xml";


    /**
     * 主数据源,Primary注解必须增加,它表示该数据源为默认数据源
     * 项目中还可能存在其他的数据源,如获取时不指定名称,则默认获取这个数据源,如果不添加,则启动时候回报错
     */
    @Primary
    @Bean(name = "masterDataSource")
    // 读取spring.datasource.master前缀的配置文件映射成对应的配置对象
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource dataSource() {
        DataSource build = DataSourceBuilder.create().build();
        return build;
    }

    /**
     * 事务管理器,Primary注解作用同上
     */
    @Bean(name = "masterTransactionManager")
    @Primary
    public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * session工厂,Primary注解作用同上
     */

    @Bean(name = "masterSqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MasterDataSourceConfig.MAPPER_LOCATION));
        // 设置驼峰命名
        Objects.requireNonNull(sessionFactoryBean.getObject()).getConfiguration().setMapUnderscoreToCamelCase(true);
        return sessionFactoryBean.getObject();
    }

}

2、副数据源相关配置:主要是指定数据源、扫描的 mapper 地址、事务管理器等信息。

less 复制代码
@Configuration
// 指定从数据库扫描对应的Mapper文件,生成代理对象
@MapperScan(basePackages = "com.diary.it.multi.datasource.mapper2", sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDataSourceConfig {
    // mapper.xml所在地址
    private static final String MAPPER_LOCATION = "classpath*:mapper2/*.xml";

    /**
     * 数据源
     */
    @Bean(name = "slaveDataSource")
    // 读取spring.datasource.slave前缀的配置文件映射成对应的配置对象
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource dataSource() {
        DataSource build = DataSourceBuilder.create().build();
        return build;
    }


    /**
     * 事务管理器
     */
    @Bean(name = "slaveTransactionManager")
    public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * session工厂
     */

    @Bean(name = "slaveSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(SlaveDataSourceConfig.MAPPER_LOCATION));
        // 设置驼峰命名
        Objects.requireNonNull(sessionFactoryBean.getObject()).getConfiguration().setMapUnderscoreToCamelCase(true);
        return sessionFactoryBean.getObject();
    }

}

🔵 4.5 执行结果

完成上面的步骤后,就跟我们平常写业务逻辑的方式一样,在 service 中写业务逻辑,在 mapper 中写 sql 语句等,下面看看执行结果!

🟣 4.6 整合中遇到的问题

看完上面的教程,是不是发现其实整合多数据源其实也挺简单的!但是,完全按照教学流程整合还是遇到各种问题的现象真的太常见了,下面博主就总结下整合中遇到的各种问题,如果你在整合过程中也遇到了,可以直接按照博主的解决方案来哦 (贴心吧!)。

问题 1、出现 jdbcUrl is required with driverClassName 异常

原因: SpringBoot2.x 后默认的数据库连接池就是 HikariCP(号称史上最快,性能最高),HikariCP连接池中命名规则和其他的连接池不太一样,指定连接数据库的地址时,它使用的是jdbc-url而不是url,所以如果我们不指定数据库连接池如druid而使用springboot默认的连接池的话,需要将配置中连接数据库的url改成jdbc-url属性。

问题 2、 出现 Invalid bound statement (not found) 异常

原因:

(1)、在定义数据源配置信息时没有指定 SqlSessionFactoryBean 扫描的 mapper.xml 文件的位置即 sessionFactoryBean.setMapperLocations(xxx)。

(2)、mapper.xml 文件中 namespace 属性对应的路径不准确或者对应方法的 id 名称、parameterType 属性不对

(3)、xxxMapper.java 的方法返回值是 List, 而 select 元素没有正确配置 ResultMap, 或者只配置 ResultType

问题 3、 出现 required a single bean, but 2 were found 异常

原因: 因为我们在指定主副数据源配置时已经使用 MapperScan 注解进行扫描对应的 mapper.java,此时被扫描到的 mapper.java 已经生成代理类到 Spring 容器,如果此时在启动类中再使用 MapperScan 扫描则会成出现上面的问题 (奇怪的是:这个问题我换一台电脑就不报错了,所以出现这个问题先按照这个方案解决吧)

问题 4、 主数据源配置类中为什么添加 Primary 注解

原因: 因为整合了多数据源,所以 DataSource、PlatformTransactionManager 等实例都会注入多个到 Spring 容器中,Primary 注解的作用就是:当我们使用自动配置的方式如Autowired注入Bean时,如果这个Bean有多个候选者,如果其中一个候选者具有@Primary注解修饰,该候选者会被选中,作为自动配置的值。

问题 5、 com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver 的区别

原因: 细心的小伙伴会发现,在数据库配置中 driver-class-name 属性的值为 com.mysql.cj.jdbc.Driver,其实 com.mysql.jdbc.Driver 是 对应 mysql-connector-java 5 驱动的,com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6 及之后的数据库驱动的,如果使用了 6.x 后的 mysql 数据库驱动还继续使用 com.mysql.jdbc.Driver 则启动时会报 deprecated(过时的),同时使用 mysql6.x 后的驱动需要指定时区 serverTimezone:

🚲 五、SpringBoot+Mybatis-Plus 整合多数据源

上面 Mybatis 使用分包的方式整合多数据源多少还是有些麻烦的,但是使用 MyBatis-Plus 就比较简单了,MyBatis-Plus官方就支持了多数据源,使用的时候只需要一个注解就可以实现,整合多数据源的时候推荐使用该种方式。

🟥 5.1 说明

本次案例涉及到的代码比较多,因此文章只贴出部分,全部案例代码已经上传到 Gitee,需要者可直接访问:【实战 - SpringBoot 结合 MyBatis-Plus 整合多数据源】,mybatis-plus 多数据源支持

项目结构如下:

🟧 5.2 涉及的依赖包

  • spring-boot-starter-web -- web 相关支持
  • mybatis-plus-boot-starter-- springboot 整合 mybatis-plus 依赖
  • dynamic-datasource-spring-boot-starter -- mybatis-plus 管理数据源依赖
  • mysql-connector-java -- mysql 数据驱动
  • lombok -- 自动生成实体类常用方法依赖包
  • hutool-all -- 常用方法封装依赖包
xml 复制代码
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.4.5</version>
        </dependency>

🟨 5.3 相关配置

yaml 复制代码
# 启动端口
server:
  port: 9091

# 项目名称
spring:
  application:
    name: multi-datasource-instance2
  datasource:
    # 采用动态选取
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        # 主数据库
        master:
          url: jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        # 副数据库
        slave:
          url: jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

🟩 5.4 使用方式

🟦 5.5 执行结果

🚀 六、写在最后

文章中所有代码都已上传到 Gitee,有需要可以自取 (后面会传到 CSDN 免费下载),如果有帮助不要忘了 star 哦,后面会有更多实战文章 (顺便透露下下篇文章是:关于Ftp文件上传到服务器和下载到本地的实战),Gitee 项目直通车如下:

1、SpringBoot+MyBatis 整合多数据源

2、SpringBoot+MyBatis-Plus 整合多数据源

最近这段时间一直忙着整理技术圈子资源,所以更文比较少,现在技术圈子资源已经初步整理完毕,后面会陆续恢复更文速度。【技术圈子】中有免费面试资源、简历模板、年终汇报PPT、CSDN VIP下载资源等等,感兴趣者可以查看主页领取

相关推荐
追逐时光者8 分钟前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_8 分钟前
敏捷开发流程-精简版
前端·后端
苏打水com1 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧2 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧2 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧2 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧2 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧2 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng3 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6014 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring