🚐 一、前言
-
大家好,我是小诚,不知不觉上一次更文已经是 20 多天前了!其实这段时间也一直没有闲着,
一个是在梳理之前的文章知识和资源,用于搭建技术圈子,另外一个就是在思考自己的一个输出方向,社区发展得很迅速,热榜各种各类的文章都有,深思熟虑后,还是坚持文章在精不在多,质量标准更加重要,所以今后博文的方向会更加偏向实战和经验,争取分享更加有价值的博文!
-
如果文章对你有帮助,可以帮忙一键三连和专栏订阅哦!
技术圈子经过这段时间的筹划,已经初步成型!有兴趣、志同道合的小伙伴可以查看左边导航栏的技术圈子介绍,期待你们的加入! -
本篇文章重点介绍 SpringBoot 集合 MyBatis 和 MyBatis-Plus 整合多数据源方面的知识!
🚅 二、专栏推荐
良心推荐: 下面的相关技术专栏还在免费分享哦,大家可以帮忙点点订阅哦!
🚔 三、整合多数据源需要了解的知识
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 项目直通车如下:
2、SpringBoot+MyBatis-Plus 整合多数据源
最近这段时间一直忙着整理技术圈子资源,所以更文比较少,现在技术圈子资源已经初步整理完毕,后面会陆续恢复更文速度。【技术圈子】中有免费面试资源、简历模板、年终汇报PPT、CSDN VIP下载资源等等,感兴趣者可以查看主页领取