SpringBoot + Mybatis 实现多数据源原来如此简单

1、为什么需要整合多数据源

在开发的过程中,我们可能会遇到一个工程使用多个数据源的情况,总体而言分为以下几个原因

a、数据隔离:将不同的数据存储在不同的数据库中,如多租户场景

b、性能优化:将数据分散到多个数据库中,提高系统的性能。常见的如读写分离,将读操作分散到读库中,减轻主数据库的负载,提高读取操作的性能

c、业务场景:某些业务场景可能需要使用其他数据库中的数据,这种场景也可以通过调用第三方 rpc 接口获取数据

2、实现多数据源过程

a、maven依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.7.1</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.7.RELEASE</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
b、创建动态数据源对象
// 多数据源持有对象
public class DBContextHolder {public static final String DB_PRIMARY = "primaryDataSource";public static final String DB_SECOND = "secondDataSource";private static ThreadLocal<String> contextHolder = new ThreadLocal();public static String getDB() {return contextHolder.get();}public static void setDB(String dbName) {DBContextHolder.contextHolder.set(dbName);}public static void cleanDB() {contextHolder.remove();}
}// 决定使用那个数据源
public class DynamicDataSource extends AbstractRoutingDataSource {@Nullable@Overrideprotected Object determineCurrentLookupKey() {return DBContextHolder.getDB();}
}
c、在mybatis配置 sqlSessionFactory 中指定动态数据源
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();//设置数据源sqlSessionFactory.setDataSource(dynamicDataSource);sqlSessionFactory.setTypeAliasesPackage("com.jyt.service.testdb.entity");sqlSessionFactory.setGlobalConfig(globalConfiguration());sqlSessionFactory.setPlugins(new Interceptor[]{ //OptimisticLockerInterceptor(),performanceInterceptor()paginationInterceptor()});sqlSessionFactory.setConfiguration(mybatisConfiguration());return sqlSessionFactory.getObject();
}
d、通过 aop 动态指定 DBContextHolder 中的 dbName
// 设置默认数据源,不指定时使用
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DB {String name() default DBContextHolder.DB_PRIMARY;
}@Slf4j
@Aspect
@Component
public class DynamicAop implements Ordered {// 此处也可以按自己的想法实现按目录区分@Around("@annotation(db)")public void around(ProceedingJoinPoint joinPoint, DB db) throws Throwable {try {DBContextHolder.setDB(db.name());log.info("setDB {}", DBContextHolder.getDB());joinPoint.proceed();} finally {log.info("threadLocal cleanDB {}", DBContextHolder.getDB());DBContextHolder.cleanDB();}}/**aop要在spring事务开启之前设置*/@Overridepublic int getOrder() {return 1;}
}
e、准备数据源配置信息
spring:datasource:druid:primary:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/basefun?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8username: rootpassword: rootsecond:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/basefun2?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8username: rootpassword: root
@Configuration
public class DatabaseConfig {@Bean(DBContextHolder.DB_PRIMARY)@ConfigurationProperties("spring.datasource.druid.primary")public DruidDataSource primaryDataSource() {return new DruidDataSource();}@Bean(DBContextHolder.DB_SECOND)@ConfigurationProperties("spring.datasource.druid.second")public DataSource secondDataSource() {return new DruidDataSource();}@Beanpublic DynamicDataSource dynamicDataSource() {DynamicDataSource dynamicDataSource = new DynamicDataSource();// 维护了所有的数据源列表dynamicDataSource.setTargetDataSources(ImmutableMap.of(DBContextHolder.DB_PRIMARY, primaryDataSource(), DBContextHolder.DB_SECOND, secondDataSource()));// 设置默认使用的数据源dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());return dynamicDataSource;}
}

至此配置工作已经完成,启动既可以验证多数据源了

@Service
public class TestDBService {@Resourceprivate StudentDao studentDao;@DB@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void testDB() {studentDao.insert(new Student().setAge(10).setName("张三"));}@DB(name = DBContextHolder.DB_SECOND)@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void testDB1() throws Exception {studentDao.insert(new Student().setAge(11).setName("里斯"));//int i = 1 / 0; 回滚 保存失败,上面执行成功}
}

3、分析下 spring 是如何帮我们实现多数据源的 ?

     首先我们看下  DynamicDataSource#determineCurrentLookupKey 何时会被调用 

       如图所示,sqlSessionFactory.getObject() 初始化时会调用 afterPropertiesSet() 方法,在这个方法中集中初始化,点进去查看源码,我们发现在MybatisSqlSessionFactoryBean#buildSqlSessionFactory 中会我们调用我们指定数据源的 getConnection 方法

       而 spring 提供的 AbstractRoutingDataSource#determineTargetDataSource 会回调我们接口,获取数据源对应的 key,从 resolvedDataSources(map)中获取数据源返回

       在看下 resolvedDataSources 的初始化,会使用我们在 DatabaseConfig#dynamicDataSource 中指定的 setTargetDataSources 全部的数据源列表

       这也是为什么我们需要通过 aop 动态修改 DBContextHolder 中的 key( dbName) 的原因,同理程序在运行时获取数据源时也是通过 DynamicDataSource#determineCurrentLookupKey 返回的 key 来决策使用那个数据源

以上如有不清楚或不描述不恰当之处,还请批评指正,感谢 

具体源码:DBProject: DB 多数据源集成技术选型:springboot + druid + mybatisplus + mysql - Gitee.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/606726.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【野火i.MX6ULL开发板】利用microUSB线烧入Debian镜像

0、前言 烧入Debian镜像有两种方式&#xff1a;SD卡、USB SD卡&#xff1a;需要SD卡&#xff08;不是所有型号都可以&#xff0c;建议去了解了解&#xff09;、SD卡读卡器 USB&#xff1a;需要microUSB线 由于SD卡的网上资料很多了&#xff0c;又因为所需硬件&#xff08;SD卡…

Spring应用的部署与管理

一、前言 部署是将开发好的应用发布到服务器上&#xff0c;使其能够被用户访问的关键步骤。Spring框架提供了灵活的部署选项&#xff0c;本文将介绍Spring应用的常见部署方式和一些建议&#xff0c;帮助开发者顺利将应用投放到生产环境。 二、传统部署方式&#xff1a;WAR包 传…

09Bean的生命周期及循环依赖

Spring其实就是一个管理Bean对象的工厂。它负责对象的创建&#xff0c;对象的销毁等。 所谓的生命周期就是&#xff1a;对象从创建开始到最终销毁的整个过程。 Bean的生命周期之5步 ● 第一步&#xff1a;实例化Bean(无参构造方法执行) ● 第二步&#xff1a;Bean属性赋值(注…

基于YOLOv7算法的高精度实时19类动物目标检测识别系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法的高精度实时19类动物目标检测系统可用于日常生活中检测与定位19类动物目标&#xff08;水牛、 斑马、 大象、 水豚、 海龟、 猫、 奶牛、 鹿、 狗、 火烈鸟、 长颈鹿、 捷豹、 袋鼠、 狮子、 鹦鹉、 企鹅、 犀牛、 羊和老虎&#xff09;&#x…

[Kubernetes]5. k8s集群StatefulSet详解,以及数据持久化(SC PV PVC)

前面通过deployment结合service来部署无状态的应用,下面来讲解通过satefulSet结合service来部署有状态的应用 一.StatefulSet详解 1.有状态和无状态区别 无状态: 无状态(stateless)、牲畜(cattle)、无名(nameless)、可丢弃(disposable) 有状态: 有状态(stateful)、宠物(pet)…

【Python机器学习】构造决策树

通常来说&#xff0c;构造决策树直到所有叶结点都是纯的叶结点&#xff0c;但这会导致模型非常复杂&#xff0c;并且对于训练数据高度过拟合。 为了防止过拟合&#xff0c;有两种常见策略&#xff1a; 1、尽早停止树的生长&#xff0c;也叫预剪枝 2、先构造树&#xff0c;但…

系统架构设计师教程(十)软件可靠性基础知识

软件可靠性基础知识 10.1 软件架构演化和定义的关系10.1.1 演化的重要性10.1.2 演化和定义的关系 10.2 面向对象软件架构演化过程10.2.1 对象演化10.2.2 消息演化10.2.3 复合片段演化10.2.4 约束演化 10.3 软件架构演化方式的分类10.3.1 软件架构演化时期10.3.2 软件架构静态演…

免费服务器腾讯云_腾讯云免费服务器申请流程(2024更新)

腾讯云免费服务器申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云百科txybk.com分享2024年最新腾讯云免费服务器…

径向基函数插值

一、径向基函数的定义 如果 ∣ ∣ x 1 ∣ ∣ ∣ ∣ x 2 ∣ ∣ ||x_1||||x_2|| ∣∣x1​∣∣∣∣x2​∣∣&#xff0c;那么 ϕ ( x 1 ) ϕ ( x 2 ) \phi(x_1)\phi(x_2) ϕ(x1​)ϕ(x2​) 的函数 ϕ \phi ϕ 就是径向函数&#xff0c;即仅由 r ∣ ∣ x ∣ ∣ r||x|| r∣∣…

金蝶EAS pdfviewlocal 任意文件读取漏洞复现

0x01 产品简介 金蝶EAS 为集团型企业提供功能全面、性能稳定、扩展性强的数字化平台&#xff0c;帮助企业链接外部产业链上下游&#xff0c;实现信息共享、风险共担&#xff0c;优化生态圈资源配置&#xff0c;构筑产业生态的护城河&#xff0c;同时打通企业内部价值链的数据链…

《中学物理奇妙日志——30天物理学探索之旅》提纲

《中学物理奇妙日志——30天物理学探索之旅》提纲 第一部分&#xff1a;物理学基础&#xff08;第1-5天&#xff09; 第一天&#xff1a;引言 - 从生活中的物理现象出发&#xff0c;阐述物理学的定义与重要性 子主题&#xff1a;物理学的历史、发展及在现代生活中的广泛应用 …

视图与索引连表查询内/外联和子查询

1.视图 先介绍一下视图&#xff1a; 从SQL的角度来看&#xff0c;视图和表是相同的&#xff0c;两者的区别在于表中存储的是实际的数据&#xff0c;而视图中保存的是SELECT语句&#xff08;视图本身并不存储数据&#xff09;。 使用视图可以轻松完成跨多表查询数据等复杂操作…

大学生如何当一个程序员——第三篇:热门专业学习之路5

第三篇&#xff1a;热门专业学习之路5 1.WEB前端快速入门2.JavaScript基础与深入解析3.jQuery应用与项目开发4.PHP、数据库编程与设计5. Http服务于Ajax编程6. 做一个阶段项目7. H5新特性与移动端开发8.高级框架9.微信小程序 各位小伙伴想要博客相关资料的话关注公众号&#xf…

R语言(12):绘图

12.1 创建图形 12.1.1 plot函数 plot(c(1,2,3),c(1,2,4)) plot(c(1,2,3),c(1,2,4),"b") plot(c(-3,3),c(-1,5),"n",xlab "x",ylab "y")12.1.2 添加线条&#xff1a;abline()函数 x <- c(1,2,3) y <- c(1,3,8) plot(x,y) lm…

算法28:力扣64题,最小路径和------------样本模型

题目&#xff1a; 给定一个二维数组matrix&#xff0c;一个人必须从左上角出发&#xff0c;最后到达右下角 。沿途只可以向下或者向右走&#xff0c;沿途的数字都累加就是距离累加和 * 返回累加和最小值 思路&#xff1a; 1. 既然是给定二维数组matrix&#xff0c;那么二维数…

寒假前端第一次作业

1、用户注册&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>用户注册</title> …

C++中的返回值优化(RVO)

一、命名返回值优化&#xff08;NRVO&#xff09; 是Visual C2005及之后版本支持的优化。 具体来说&#xff0c;就是一个函数的返回值如果是一个对象。那么&#xff0c;正常的返回语句的执行过程是&#xff0c;把这个对象从当前函数的局部作用域&#xff0c;或者叫当前函数的…

视频AI智剪方法:快速批量处理视频,批量剪辑视频的操作

随着科技的飞速发展&#xff0c;视频内容已是获取信息和娱乐的主要方式之一。对于视频创作者和内容生产者来说&#xff0c;如何快速、高效地处理和剪辑大量视频已成为一项重要的需求。现在借助AI技术的不断发展&#xff0c;可以更加智能、高效的处理视频。下面来看云炫AI智剪如…

VS2022 | 显示Unreal Engine日志

VS2022 | 显示Unreal Engine日志 视图 -> 其他窗口 -> Unreal Engine日志 视图 -> 其他窗口 -> Unreal Engine日志

【debug】为什么ansible中使用command出错

碎碎念 在使用ansible执行command的时候&#xff0c;遇到执行会出错的command 比如执行source打算读取环境变量的时候 错误提示为&#xff1a; 没有那个文件或目录:source 一开始以为是错误提示有问题&#xff0c;一直在testrc的路径上检查&#xff0c;但是同样一行命令使用…