SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

本文是SpringBoot第27讲,在某些场景下,Springboot需要使用多个数据源,以及某些场景会需要多个数据源的动态切换。本文主要介绍上述场景及 SpringBoot+MyBatis实现多个数据源的方案和示例

文章目录

  • SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源
    • 1、知识准备
      • 1.1、什么场景会出现多个数据源?
      • 1.2、常见的多数据源的实现思路?
    • 2、简单示例
      • 2.1、分包方式实现
      • 2.2、针对场景二:主库和从库分离(读写分离)
    • 3、示例源码

1、知识准备

需要了解多数据源出现的场景和对应的多数据源集成思路。

1.1、什么场景会出现多个数据源?

一般而言有如下几种出现多数据源的场景。

  • 场景一:不同的业务涉及的表位于不同的数据库

随着业务的拓展,模块解耦,服务化的拆分等,不同的业务涉及的表会放在不同的数据库中。

  • 例如商品主库、商品审核库、商品附属库需要在一个微服务中使用;

  • daily环境和线上环境数据库、表、列、索引比对,同时需要访问daily环境和线上环境

  • 场景二:主库和从库分离(读写分离)

主从分离等相关知识请参考这篇文章: MySQL第七讲:MySQL分库分表详解

  • 场景三:数据库的分片

数据库的分片相关知识和方案请参考:SpringBoot集成MySQL - 分库分表ShardingJDBC

  • 场景四:多租户隔离

所有数据库表结构一致,只是不同客户的数据放在不同数据库中,通过数据库名对不同客户的数据隔离。这种场景有一个典型的叫法:多租户

PS:除了这种多租户除了用不同的数据库隔离不同客户数据外,还会通过额外的表字段隔离(比如tenant_id字段,不同的tenant_id表示不同的客户),对应的实现方式和案例可以参考 SpringBoot第30讲:SpringBoot集成MySQL - MyBatis-Plus基于字段隔离的多租户

1.2、常见的多数据源的实现思路?

应对上述出现的场景,多数据源方式如何实现呢?

  • 针对场景一:不同的业务涉及的表位于不同的数据库

    • 首先,出现这种场景且在一个模块中设计多数据源时,需要考虑当前架构的合理性,因为从设计的角度而言不同的业务拆分需要对应着不同的服务/模块
    • 其次,我们会考虑不同的package去隔离,不同的数据源放在不同的包下的代码中
  • 针对场景二:主库和从库分离(读写分离)

这种场景下我们叫动态数据源,通常方式使用AOP方式拦截+ThreadLocal切换。

2、简单示例

2.1、分包方式实现

分包方式实现:

1、在publish.properties中配置两个数据库:

# dbsource1
datasource.url=jdbc:mysql://xxx?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
datasource.username=***
datasource.password=***
datasource.minIdle=5
datasource.maxActive=20# dbsource2
datasource.daily.url=jdbc:mysql://***?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
datasource.daily.username=***
datasource.daily.password=****
datasource.daily.minIdle=5
datasource.daily.maxActive=10

2、在application.yml中配置好映射关系

Spring:datasource1:url: ${datasource.url}username: ${datasource.username}password: ${datasource.password}minIdle: ${datasource.minIdle}maxActive: ${datasource.maxActive}# datasource config 2datasource2:url: ${datasource.daily.url}username: ${datasource.daily.username}password: ${datasource.daily.password}minIdle: ${datasource.daily.minIdle}maxActive: ${datasource.daily.maxActive}

3、建立连个数据源的配置文件:
第一个配置文件:

//表示这个类为一个配置类
@Configuration
// 配置mybatis的接口类放的地方
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper.test01", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSourceConfig1 {// 将这个对象放入Spring容器中@Bean(name = "test1DataSource")// 表示这个数据源是默认数据源@Primary// 读取application.properties中的配置参数映射成为一个对象// prefix表示参数的前缀@ConfigurationProperties(prefix = "spring.datasource1")public DataSource getDateSource1() {return DataSourceBuilder.create().build();}@Bean(name = "test1SqlSessionFactory")// 表示这个数据源是默认数据源@Primary// @Qualifier表示查找Spring容器中名字为test1DataSource的对象public SqlSessionFactory test1SqlSessionFactory(@Qualifier("test1DataSource") DataSource datasource)throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(datasource);bean.setMapperLocations(// 设置mybatis的xml所在位置new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/test01/*.xml"));return bean.getObject();}@Bean("test1SqlSessionTemplate")// 表示这个数据源是默认数据源@Primarypublic SqlSessionTemplate test1sqlsessiontemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sessionfactory) {return new SqlSessionTemplate(sessionfactory);}
}

第二个配置文件:

@Configuration
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper.test02", sqlSessionFactoryRef = "test2SqlSessionFactory")
public class DataSourceConfig2 {@Bean(name = "test2DataSource")@ConfigurationProperties(prefix = "spring.datasource2")public DataSource getDateSource2() {return DataSourceBuilder.create().build();}@Bean(name = "test2SqlSessionFactory")public SqlSessionFactory test2SqlSessionFactory(@Qualifier("test2DataSource") DataSource datasource)throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(datasource);bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/test02/*.xml"));return bean.getObject();}@Bean("test2SqlSessionTemplate")public SqlSessionTemplate test2sqlsessiontemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sessionfactory) {return new SqlSessionTemplate(sessionfactory);}
}

注意:

  • 1、@Primary这个注解必须要加,因为不加的话spring将分不清楚哪个为主数据源(默认数据源)

  • 2、mapper的接口、xml形式以及dao层都需要两个分开,目录如图:

  • 在这里插入图片描述

  • 3、bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(“XXXX”)); mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致的,具体看情况吧,注意一下就行,问题不大的)

  • 4、在service层中根据不同的业务注入不同的dao层。

  • 5、如果是主从复制- -读写分离:比如test01中负责增删改,test02中负责查询。但是需要注意的是负责增删改的数据库必须是主库(master)

  • 6、如果是分布式结构的话,不同模块操作各自的数据库就好,test01包下全是test01业务,test02全是test02业务,但是如果test01中掺杂着test02的编辑操作,这时候将会产生事务问题:即test01中的事务是没法控制test02的事务的,这个问题在之后的博客中会解决。

2.2、针对场景二:主库和从库分离(读写分离)

这种场景下我们叫动态数据源,通常方式使用AOP方式拦截+ThreadLocal切换。(本文的示例主要针对这种场景)

简介: 用这种方式实现多数据源的前提必须要清楚两个知识点:AOP原理和 AbstractRoutingDataSource抽象类。

1、AOP: 不切当的说就是相当于拦截器,只要满足要求的都会被拦截过来,然后进行一些列的操作。具体需要自己去体会。

2、AbstractRoutingDataSource: 这个类是实现多数据源的关键,他的作用就是动态切换数据源,

  • 实质:有多少个数据源就存多少个数据源在targetDataSources(是AbstractRoutingDataSource的一个map类型的属性,其中value为每个数据源,key表示每个数据源的名字)这个属性中,然后根据 determineCurrentLookupKey()这个方法获取当前数据源在map中的key值,然后determineTargetDataSource()方法中动态获取当前数据源,如果当前数据源不存并且默认数据源也不存在就抛出异常。

  • 存在就抛出异常。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {//多数据源map集合private Map<Object, Object> targetDataSources;//默认数据源private Object defaultTargetDataSource;//其实就是targetDataSources,后面的afterPropertiesSet()方法会将targetDataSources 赋值给resolvedDataSourcesprivate Map<Object, DataSource> resolvedDataSources;private DataSource resolvedDefaultDataSource;public void setTargetDataSources(Map<Object, Object> targetDataSources) {this.targetDataSources = targetDataSources;}protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = this.determineCurrentLookupKey();DataSource 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 + "]");} else {return dataSource;}}protected abstract Object determineCurrentLookupKey();
}

具体实现:

1、定义一个动态数据源: 继承AbstractRoutingDataSource 抽象类,并重写determineCurrentLookupKey()方法

public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();return dataBaseType;}
}

2、创建一个切换数据源类型的类: ThreadLocal这个知识点可以参考我的博客:JUC第六讲:ThreadLocal/InheritableThreadLocal详解/TTL-MDC日志上下文实践 就是为了线程的安全性,每个线程之间不会相互影响。

public class DataSourceType {public enum DataBaseType {TEST01, TEST02}// 使用ThreadLocal保证线程安全private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();// 往当前线程里设置数据源类型public static void setDataBaseType(DataBaseType dataBaseType) {if (dataBaseType == null) {throw new NullPointerException();}System.err.println("[将当前数据源改为]:" + dataBaseType);TYPE.set(dataBaseType);}// 获取数据源类型public static DataBaseType getDataBaseType() {DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.TEST01 : TYPE.get();System.err.println("[获取当前数据源的类型为]:" + dataBaseType);return dataBaseType;}// 清空数据类型public static void clearDataBaseType() {TYPE.remove();}
}

3、定义多个数据源: 怎么定义就不多说了,和方法一是一样的,主要是将定义好的多个数据源放在动态数据源中。

@Configuration
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper", sqlSessionFactoryRef = "SqlSessionFactory")
public class DataSourceConfig {@Primary@Bean(name = "test1DataSource")@ConfigurationProperties(prefix = "spring.datasource1")public DataSource getDateSource1() {return DataSourceBuilder.create().build();}@Bean(name = "test2DataSource")@ConfigurationProperties(prefix = "spring.datasource.test2")public DataSource getDateSource2() {return DataSourceBuilder.create().build();}@Bean(name = "dynamicDataSource")public DynamicDataSource DataSource(@Qualifier("test1DataSource") DataSource test1DataSource,@Qualifier("test2DataSource") DataSource test2DataSource) {Map<Object, Object> targetDataSource = new HashMap<>();targetDataSource.put(DataSourceType.DataBaseType.TEST01, test1DataSource);targetDataSource.put(DataSourceType.DataBaseType.TEST02, test2DataSource);DynamicDataSource dataSource = new DynamicDataSource();dataSource.setTargetDataSources(targetDataSource);dataSource.setDefaultTargetDataSource(test1DataSource);return dataSource;}@Bean(name = "SqlSessionFactory")public SqlSessionFactory test1SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dynamicDataSource);bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));return bean.getObject();}
}

4、定义AOP: 就是不同业务切换不同数据库的入口。如果觉得execution太长不愿意写,就可以定义一个注解来实现。可参考于我的博客:Java基础知识第二讲:Java开发手册/注解/反射/ IO

@Aspect
@Component
public class DataSourceAop {@Before("execution(* com.mzd.multipledatasources.service..*.test01*(..))")public void setDataSource2test01() {System.err.println("test01业务");DataSourceType.setDataBaseType(DataBaseType.TEST01);}@Before("execution(* com.mzd.multipledatasources.service..*.test02*(..))")public void setDataSource2test02() {System.err.println("test02业务");DataSourceType.setDataBaseType(DataBaseType.TEST02);}
}

整体目录如图:
在这里插入图片描述

3、示例源码

todo

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

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

相关文章

策略模式(Strategy)

定义 策略是一种行为设计模式&#xff0c;它能让你定义一系列算法&#xff0c;并将每种算法分别放入独立的类中&#xff0c;以使算法的对象能够相互替换。 前言 1. 问题 你打算为游客们创建一款导游程序。该程序的核心功能是提供美观的地图&#xff0c;以帮助用户在任何城市…

前端vue入门(纯代码)19

不管何时何地&#xff0c;永远保持热爱&#xff0c;永远积极向上&#xff01;&#xff01;&#xff01; 【21.Vue中的插槽slot】 问题&#xff1a;插槽&#xff08;slot&#xff09;是什么&#xff1f;兄弟们也可以点这里去看这位兄弟的博客&#xff0c;写的比我详细&#xff…

【Flink】Flink 中的时间和窗口之水位线(Watermark)

1. 时间语义 这里先介绍一下什么是时间语义&#xff0c;时间语义在Flink中是一种很重要的概念&#xff0c;下面介绍的水位线就是基于时间语义来讲的。 在Flink中我们提到的时间语义一般指的是事件时间和处理时间&#xff1a; 处理时间(Processing Time)&#xff0c;一般指执…

《深入浅出SSD:固态存储核心技术、原理与实战》----学习记录(三)

第3章 SSD存储介质&#xff1a;闪存 3.1 闪存物理结构 3.1.1 闪存器件原理 1978年诞生的世界上第一块固态硬盘就是基于DRAM的。但由于保存在DRAM中的数据有掉电易失性&#xff0c;当然还有成本因素&#xff0c;所以现在的固态硬盘一般都不采用DRAM&#xff0c;而是使用闪存…

【mysql环境】mysql的多种安装方法、环境配置总结

目录 第一步&#xff1a;mysql安装方法 方法一&#xff1a; 方法二&#xff1a; 方法三&#xff1a; 第二步&#xff1a;配置环境变量 第三步&#xff1a;验证是否配置成功 第一步&#xff1a;mysql安装方法 方法一&#xff1a; 下载MSI安装程序&#xff0c;进行mysql的…

C++笔记之各种sleep方法总结

C笔记之sleep总结 —— 2023年4月9日 小问 上海 code review 文章目录 C笔记之sleep总结1.std::this\_thread::sleep\_for()附&#xff1a;std::this\_thread::sleep\_for(std::chrono::duration) 2.std::this\_thread::sleep\_until()附&#xff1a;std::this\_thread::sleep\…

Microsoft Edge插件推荐:CSDN·浏览器助手

文章目录 1.简介2.安装3.总结 今天来给大家分享一个超级好用的Microsoft Edge插件&#xff0c;名为CSDN浏览器助手 1.简介 CSDN浏览器助手是一款集成本地书签、历史记录与 CSDN搜索(so.csdn.net) 的搜索工具&#xff0c;可以自定义Microsoft Edge的新标签页&#xff0c;还可以…

UDP SocketAPI

1、TCP与UDP区别 TCP&#xff1a;有连接&#xff0c;可靠传输&#xff0c;面向字节流&#xff0c;全双工 UDP&#xff1a;无连接&#xff0c;不可靠传输&#xff0c;面向数据报&#xff0c;全双工 2、UDP sockeAPI的核心类 DatagramSocket&#xff1a;相当于对socket文件进…

OpenStack(3)--vxlan网络实战

目录 一、ML2配置文件 二、上传cirros镜像 三、创建vxlan10网络 四、创建实例/同vxlan通信测试 五、不同vxlan通信测试 5.1 新建vxlan11 5.2 新建路由/添加路由接口 5.3 不同vxlan通信测试 5.4 qemu-vnc报错 六、深度剖析vxlan 七、认识 Bridge br-ex、Bridge br-in…

基于matlab基于预训练的膨胀双流卷积神经网络的视频分类器执行活动识别(附源码)

一、前言 此示例首先展示了如何使用基于预训练的膨胀 3-D &#xff08;I3D&#xff09; 双流卷积神经网络的视频分类器执行活动识别&#xff0c;然后展示了如何使用迁移学习来训练此类视频分类器使用 RGB 和来自视频的光流数据 [1]。 基于视觉的活动识别涉及使用一组视频帧预…

PHY芯片的使用(一)之基本概念讲解(MII相关)2

今天想和大家交流一下MAC和PHY之间的接口MII。 MII(Media Independent Interface )是介质无关接口。MII接口定义了在MAC层和物理层之间传送数据和控制状态等信息的接口&#xff0c;它是10M和100M兼容的接口&#xff0c;经过扩展后还可以用于1000M的MAC层与物理层的接口&#x…

【国产复旦微FMQL45教程】-小试牛刀之LED

本教程采用 FMQL7045 FPGA开发板来完成整个试验&#xff0c;板卡照片如下&#xff1a; 具有丰富的接口资源&#xff0c;系统框图如下&#xff1a; 本教程用于完成基于Vivado的FMQL45的LED实验&#xff0c;目标是能够将这款开发板PL端先跑起来。 对于纯 PL 设计&#xff0c;我们…