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
    @Override
    protected 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事务开启之前设置*/
    @Override
    public int getOrder() {
        return 1;
    }
}
e、准备数据源配置信息
spring:
    datasource:
        druid:
            primary:
                driver-class-name: com.mysql.jdbc.Driver
                url: jdbc:mysql://localhost:3306/basefun?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
                username: root
                password: root
            second:
                driver-class-name: com.mysql.jdbc.Driver
                url: jdbc:mysql://localhost:3306/basefun2?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
                username: root
                password: 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();
    }

    @Bean
    public 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 {

    @Resource
    private 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鹦鹉目标检测数据集VOC格式600张

鹦鹉&#xff0c;一种色彩鲜艳、聪明伶俐的鸟类&#xff0c;以其模仿人类语言的能力和独特的喙形而广受喜爱。 鹦鹉属于鸟纲、鹦鹉科&#xff0c;是热带和亚热带地区的常见鸟类。它们的喙弯曲呈钩状&#xff0c;非常适合啄食种子、果实和坚果等食物。鹦鹉的羽毛通常非常鲜艳&a…

DVWA-Hight-xss漏洞

首先来到DVWA高级模式下反射型xss漏洞处 开始测试 <script>alert(/xss/)</script> 发现直接使用js代码不行&#xff0c;被直接过滤稍微试探针对的过滤对象 发现这里针对 <script>标签会直接过滤 我们改用<img>标签试探是否过滤 发现这里针对img标签没…

c语言-数组指针

文章目录 前言一、字符指针二、数组指针2.1 数组指针基础2.2 数组指针作函数参数 三、void*类型指针总结 前言 在c语言基础已经介绍过关于指针的概念和基本使用&#xff0c;本篇文章进一步介绍c语言中关于指针的应用。 一、字符指针 字符指针是指向字符的指针。 结果分析&…

如何将ElementUI组件库中的时间控件迁移到帆软报表中

需求:需要将ElementUI组件库中的时间控件迁移到帆软报表中,具体为普通报表的参数面板中,填报报表的组件中,决策报表的组件与参数面板中。 这三个场景中分别需要用到帆软报表二开平台的ParameterWidgetOptionProvider,FormWidgetOptionProvider,CellWidgetOptionProvider开…

04、Kafka ------ 各个功能的作用解释(Cluster、集群、Broker、位移主题、复制因子、领导者副本、主题)

目录 启动命令&#xff1a;CMAK的用法★ 在CMAK中添加 Cluster★ 在CMAK中查看指定集群★ 在CMAK中查看 Broker★ 位移主题★ 复制因子★ 领导者副本和追随者副本★ 查看主题 启动命令&#xff1a; 1、启动 zookeeper 服务器端 小黑窗输入命令&#xff1a; zkServer 2、启动 …

Java桶排序、基数排序、剪枝算法

桶排序算法 桶排序的基本思想是&#xff1a; 把数组 arr 划分为 n 个大小相同子区间&#xff08;桶&#xff09;&#xff0c;每个子区间各自排序&#xff0c;最后合并 。计数排序是桶排序的一种特殊情况&#xff0c;可以把计数排序当成每个桶里只有一个元素的情况。 1.找出待…

数字孪生与物联网(IoT)技术的结合

数字孪生与物联网&#xff08;IoT&#xff09;技术的结合可以在多个领域实现更智能、更高效的应用。以下是数字孪生在物联网技术中的一些应用&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.实时监…

lazada越南站收款问题;lazada可以使用支付宝吗?-站斧浏览器

Lazada越南站收款问题 线上支付方式&#xff1a;Lazada越南本土店提供多种线上支付方式&#xff0c;以方便消费者完成购物支付。常见的线上支付方式包括信用卡支付、借记卡支付、电子钱包支付&#xff08;如Momo、Zalo Pay等&#xff09;以及银行转账等。商家可以根据自己的需…

[VUE]4-状态管理vuex

目录 状态管理 vuex 1、vuex 介绍 2、安装 3、使用方式 4、总结 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅长web应用开发、数据结构和算法&#xff0c;初步涉猎Python人工智…

集成电路封装基板技术

集成电路(IC)封装是伴随集成电路的发展而前进的。随着宇航、航空、机械、轻工、化工等各个行业的不断发展&#xff0c;整机也向着多功能、小型化方向变化。这样&#xff0c;就要求IC的﹐集成度越来越高&#xff0c;功能越来越复杂。相应地要求集成电路封装密度越来越大&#xf…

【Element】el-form和el-table嵌套实现表格编辑并提交表单校验

目录 一、背景 二、功能实现 2.1、el-form和el-table嵌套说明 2.2、具体代码 三、实际项目应用 3.1、增加添加与删除操作 3.2、添加和删除代码 3.4、实际效果 一、背景 页面需要用到表格采集用户数据&#xff0c;提交时进行表单校验&#xff1b;即表单中嵌套着表格&am…

散列分区(hash分区)案例

在列取值难以确定的情况下采用的分区方法 1.hash分区可以由hash键来分布 2.dba无法获知具体的数据值 3.数据的分布由oracle处理 4每个分区有自己的表空间 --建表同上一节 CREATE TABLE ware_retail_part3( id INTEGER primary key, retail_date date, ware_na…

软件测试|深入理解Python的encode()和decode()方法

简介 在Python中&#xff0c;字符串是不可变的序列对象&#xff0c;它由Unicode字符组成。当我们需要在字符串和字节之间进行转换时&#xff0c;Python提供了两个非常重要的方法&#xff1a;encode()和decode()。这两个方法允许我们在Unicode字符和字节之间进行相互转换&#…

harmonyOS 时间选择组件(TimePicker)

本文 我们来说 TimePicker 时间组件 首先 我们搭一个最基本的组件骨架 Entry Component struct Index {build() {Row() {Column() {}.width(100%)}.height(100%)} }然后 在 Column 组件内 放一个 TimePicker进去 这里 我们就可以看到 一个时间的选择器了 DatePicker 捕获当前…

【JUC进阶】13. InheritableThreadLocal

目录 1、前言 2、回顾ThreadLocal 3、InheritableThreadLocal 4、实现原理 5、线程池中的问题 6、小结 1、前言 在《【JUC基础】14. ThreadLocal》一文中&#xff0c;介绍了ThreadLocal主要是用于每个线程持有的独立变量。通俗的说就是ThreadLocal是每个线程独有的一份内…

基于ssm的双减后初小教育课外学习生活活动平台的设计与实现论文

双减后初小教育课外学习生活活动平台的设计与实现 摘 要 当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。以前学校对于课外学习活动信息的管理和控制&#xff0c;采用人工登记的方式保存相关…

Spring——基于注解的AOP配置

基于注解的AOP配置 1.创建工程 1.1.pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"…

多功能环境模拟试验系统

1、模拟自然条件下的气候环境、海洋环境、工业环境&#xff1b; 2、人工气候环境下各种工程材料、结构的耐久性试验&#xff1b; 3、人工气候环境混凝土结构热学性能及早期特性试验&#xff1b; 4、人工气候环境混凝土结构裂缝控制研究&#xff1b; 5、海洋环境下工程材料、…

模式识别实验三

实验三 一  实验名称 感知器设计 二 目的和意义 使用感知器完成线性分类任务 三 操作步骤或算法结构 数据预处理。载入数据文件&#xff08; iris.csv 文件&#xff09;中的数据&#xff0c;并将其分成样本向量矩阵X和样本分类结果向量 G \bf G G。 给 4 4 4 列向量的…

Objective-C中使用STL标准库Queue队列

1.修改.m文件为mm 2.导入queue头 #include<queue> 3.使用&#xff1a; #import <Foundation/Foundation.h> #include <cmath> #include <queue> using namespace std;int main(int argc, const char * argv[]) {autoreleasepool {NSLog("C标准…