SpringBoot实现多数据源切换

news2024/10/7 13:17:16

1. 概述


仓库地址:https://gitee.com/aopmin/multi-datasource-demo

随着项目规模的扩大和业务需求的复杂化,单一数据源已经不能满足实际开发中的需求。在许多情况下,我们需要同时操作多个数据库,或者需要将不同类型的数据存储在不同的数据库中。这时,多数据源场景成为必不可少的解决方案。

市面上常见的多数据源实现方案如下:

  • 方案1:基于Spring框架提供的AbstractRoutingDataSource。

    • 优点: 简单易用,支持动态切换数据源;适用于少量数据源情况。
    • 场景:适用于需要动态切换数据源,且数据库较少的情况。
    • 文档地址:
  • 方案2:使用MP提供的Dynamic-datasource多数据源框架。

    • 文档地址:https://baomidou.com/guides/dynamic-datasource/#dynamic-datasource
  • 方案3:通过自定义注解在方法或类上指定数据源,实现根据注解切换数据源的功能。

    • 优点: 灵活性高,能够精确地控制数据源切换;在代码中直观明了。
    • 场景: 适用于需要在代码层面进行数据源切换,并对数据源切换有精细要求的情况。
  • 方案4:使用动态代理技术,在运行时动态切换数据源,实现多数据源的切换。

    • 优点: 灵活性高,支持在运行时动态切换数据源;适合对数据源切换的逻辑有特殊需求的情况。
    • 场景: 适用于需要在运行时动态决定数据源切换策略的情况。

2. 基于SpringBoot的多数据源实现方案


1、执行sql脚本:(分别创建两个数据库,里面都提供一张user表)

-- 创建数据库ds1
CREATE DATABASE `ds1`;

-- 使用ds1数据库
USE ds1;

-- 创建user表
CREATE TABLE `user` (
    `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
    `username` VARCHAR(50) COMMENT '用户名',
    `gender` TINYINT(1) COMMENT '性别:0男,1女'
);

-- 向user表插入数据
INSERT INTO user (username, gender) VALUES 
('张三', 1),
('李四', 0),
('王五', 1);

-- 创建数据库ds2
CREATE DATABASE `ds2`;

-- 使用ds2数据库
USE ds2;

-- 创建user表
CREATE TABLE `user` (
    `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
    `username` VARCHAR(50) COMMENT '用户名',
    `gender` TINYINT(1) COMMENT '性别:0男,1女'
);

-- 向user表插入数据
INSERT INTO user (username, gender) VALUES 
('赵六', 1),
('陈七', 0),
('宝国', 1);

2、创建一个maven工程,向pom.xml中添加依赖:

<!--锁定SpringBoot版本-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.10</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <!--jdbc起步依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
    <!--test起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.20</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
      <!--jdbc起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
</dependencies>

3、编写实体类:

package cn.aopmin.entity;

import lombok.*;

/**
 * 实体类
 *
 * @author 白豆五
 * @since 2024/7/4
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
    private Integer id;
    private String username;
    private Integer gender;
}

4、创建application.yml文件,配置数据源:

spring:

  #动态数据源配置
  datasource:
    ds1:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3306/ds1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
      username: root
      password: 123456
    ds2:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3306/ds2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
      username: root
      password: 123456

logging:
  level:
    cn.aopmin: debug

5、编写数据源配置类:

package cn.aopmin.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源配置类
 * 配置多数据源和动态数据源
 *
 * @author 白豆五
 * @since 2024/7/4
 */
@Configuration
public class DataSourceConfig {

    //定义数据源1
    @Bean("ds1")
    @ConfigurationProperties(prefix = "spring.datasource.ds1")
    public DataSource ds1() {
        return DataSourceBuilder.create().build();
    }

    //定义数据源2
    @Bean("ds2")
    @ConfigurationProperties(prefix = "spring.datasource.ds2")
    public DataSource ds2() {
        return DataSourceBuilder.create().build();
    }

    //定义动态数据源
    @Bean(name = "dataSource")
    public DataSource dynamicDataSource(@Qualifier("ds1") DataSource ds1,
                                        @Qualifier("ds2") DataSource ds2) {
        //1.定义数据源map
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("ds1", ds1);
        targetDataSources.put("ds2", ds2);
        //2.实例化自定义的DynamicDataSource对象, 并设置数据源map
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        //3.设置默认数据源,未匹配上则使用默认数据源
        dynamicDataSource.setDefaultTargetDataSource(ds1);
        return dynamicDataSource;
    }

    // 通过JdbcTemplate	
    @Bean
    public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource ds) {
        return new JdbcTemplate(ds);
    }
}

6、创建DynamicDataSource动态数据类:

package cn.aopmin.config;

import cn.aopmin.common.DataSourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * AbstractRoutingDataSource(抽象的数据源路由器) 的基本原理是, 它维护了一个数据源的集合,每个数据源都有唯一的一个标识符
 * 当应用程序需要访问数据库的时候,AbstractRoutingDataSource会根据某种匹配规则(例如请求参数、用户身份等)来选择一个合适的数据源,
 * 并将请求转发给这个数据源。
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 获取数据源名称
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

7、定义一个ThreadLocal工具类:

package cn.aopmin.common;

/**
 * 使用ThreadLocal保存数据源名称
 *
 * @author 白豆五
 * @since 2024/7/4
 */
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 将数据源名称绑定到当前线程上
    public static void setDataSource(String dataSourceName) {
        contextHolder.set(dataSourceName);
    }

    // 获取当前线程上的数据源名称
    public static String getDataSource() {
        return contextHolder.get();
    }

    // 清除数据源名称
    public static void clearDataSource() {
        contextHolder.remove();
    }
}

8、创建启动类

package cn.aopmin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 *
 * @author 白豆五
 * @since 2024/7/3
 */
@SpringBootApplication
public class Demo01Application {
    public static void main(String[] args) {
        SpringApplication.run(Demo01Application.class, args);
    }
}

9、创建UserService:

package cn.aopmin.service;

import cn.aopmin.common.DataSourceContextHolder;
import cn.aopmin.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**
 * @author 白豆五
 * @since 2024/7/4
 */
@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insertDs1(User user) {
        try {
            // todo:自定义注解+SpringAop实现数据源的切换
            DataSourceContextHolder.setDataSource("ds1");
            String sql = "insert into user(username,gender) values(?,?)";
            jdbcTemplate.update(sql,user.getUsername(), user.getGender());
        } finally {
            DataSourceContextHolder.clearDataSource();
        }
    }

    public void insertDs2(User user) {
        try {
            DataSourceContextHolder.setDataSource("ds2");
            String sql = "insert into user(username,gender) values(?,?)";
            jdbcTemplate.update(sql,user.getUsername(), user.getGender());
        } finally {
            DataSourceContextHolder.clearDataSource();
        }
    }
}

10、编写测试:

package cn.aopmin.service;

import cn.aopmin.Demo01Application;
import cn.aopmin.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest(classes = {Demo01Application.class})
public class UserServiceTest {

    @Resource
    private UserService userService;


    @Test
    public void testInsertDs1() {
        User user = new User();
        user.setUsername("jack");
        user.setGender(0);
        user.setGender(1);
        userService.insertDs1(user);
    }

    @Test
    public void testInsertDs2() {
        User user = new User();
        user.setUsername("rose");
        user.setGender(1);
        userService.insertDs2(user);
    }
}

最终效果:

在这里插入图片描述


3. 基于Dynamic-datasource实现方案


mp文档:https://baomidou.com/guides/dynamic-datasource/#_top

1、创建SpringBoot工程,引入Dynamic-datasource依赖:

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

2、配置数据源:

spring:
  #多数据源配置
  datasource:
    dynamic:
      primary: master #设置默认数据源
      strict: false #是否严格检查动态数据源提供的数据库名
      datasource:
        #数据源1
        master:
          url: jdbc:mysql:///ds1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        #数据源2
        slave1:
          url: jdbc:mysql:///ds2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

3、实体类:

package cn.aopmin.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 实体类
 *
 * @author 白豆五
 * @since 2024/7/4
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private Integer gender;
}

4、业务类:

package cn.aopmin.service;

import cn.aopmin.entity.User;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**
 * 通过@DS注解切换数据源
 * @author 白豆五
 * @since 2024/7/4
 */
@Service
// @DS("master") //不加@DS注解,会使用默认数据源
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insertDs1(User user) {
        String sql = "insert into user(username,gender) values(?,?)";
        jdbcTemplate.update(sql,user.getUsername(), user.getGender());
    }

    @DS("slave1")
    public void insertDs2(User user) {
        String sql = "insert into user(username,gender) values(?,?)";
        jdbcTemplate.update(sql,user.getUsername(), user.getGender());
    }
}

4、测试类:

package cn.aopmin.service;


import cn.aopmin.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
public class UserServiceTest2 {

    @Resource
    private UserService userService;


    @Test
    public void testInsertDs1() {
        User user = new User();
        user.setUsername("jack");
        user.setGender(0);
        user.setGender(1);
        userService.insertDs1(user);
    }

    @Test
    public void testInsertDs2() {
        User user = new User();
        user.setUsername("rose");
        user.setGender(1);
        userService.insertDs2(user);
    }
}

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

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

相关文章

MyBatis-Plus-实用的功能自动填充字段

前言: java项目用到了mybatis-plus&#xff0c;在一些类里面需要在更新时候&#xff0c;统一设置&#xff0c;修改人&#xff0c;修改ID&#xff0c;修改时间。新增时候设置 创建人&#xff0c;创建时间等 基础类&#xff1a; Data public abstract class BaseModel implements…

昇思25天学习打卡营第18天 | K近邻算法实现红酒聚类

1、实验目的 了解KNN的基本概念&#xff1b;了解如何使用MindSpore进行KNN实验。 2、K近邻算法原理介绍 K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;最初由 Cover和Hart于1968年提出(Cover等人,1967)&#…

身体(body)的觉醒

佛&#xff0c;是一个梵文的汉语音译词&#xff0c;指觉醒者。 何谓觉醒&#xff1f;什么的觉醒&#xff1f;其实很简单&#xff0c;就是身体的觉醒。 佛的另一个名字&#xff0c;叫菩提&#xff0c;佛就是菩提&#xff0c;菩提老祖&#xff0c;就是佛祖。 body&#xff0c;即…

如何优化 PostgreSQL 中对于复杂数学计算的查询?

文章目录 一、理解复杂数学计算的特点二、优化原则&#xff08;一&#xff09;索引优化&#xff08;二&#xff09;查询重写&#xff08;三&#xff09;数据库配置调整&#xff08;四&#xff09;使用数据库内置函数的优势 三、具体的优化方案和示例&#xff08;一&#xff09;…

数据结构算法-排序(一)-冒泡排序

什么是冒泡排序 冒泡排序&#xff1a;在原数组中通过相邻两项元素的比较&#xff0c;交换而完成的排序算法。 算法核心 数组中相邻两项比较、交换。 算法复杂度 时间复杂度 实现一次排序找到最大值需要遍历 n-1次(n为数组长度) 需要这样的排序 n-1次。 需要 (n-1) * (n-1) —…

基于springboot+vue+uniapp的高校宿舍信息管理系统小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

几款电脑端能够运行的AI大模型聊天客户端

Ollama Ollama 是一个用于在本地运行和管理大型语言模型的工具。它支持多种流行模型的下载和本地运行&#xff0c;包括 LLaMA-2、CodeLLaMA、Falcon 和 Mistral 。Ollama 提供了一个简单、轻量级和可扩展的解决方案&#xff0c;使得用户可以以最简单快速的方式在本地运行大模型…

LabVIEW透视变换

透视变换概述源程序在www.bjcyck.com下载 透视变换是一种几何变换&#xff0c;用于对图像进行扭曲&#xff0c;使其看起来从不同角度拍摄。这在计算机视觉和图像处理领域非常重要&#xff0c;例如在投影校正和图像配准中。LabVIEW提供了强大的图像处理工具&#xff0c;利用其V…

阿里通义音频生成大模型 FunAudioLLM 开源!

01 导读 人类对自身的研究和模仿由来已久&#xff0c;在我国2000多年前的《列子汤问》里就描述了有能工巧匠制作出会说话会舞动的类人机器人的故事。声音包含丰富的个体特征及情感情绪信息&#xff0c;对话作为人类最常使用亲切自然的交互模式&#xff0c;是连接人与智能世界…

gcc的编译C语言的过程

gcc的简介 GCC&#xff08;GNU Compiler Collection&#xff09;是由GNU项目开发和维护的一套开源编程语言编译器集合。它支持多种编程语言&#xff0c;包括但不限于C、C、Objective-C、Fortran、Ada等。GCC被广泛应用于编译和优化各种程序&#xff0c;是许多开发者和组织的首选…

【Unity数据交互】如何Unity中读取Ecxel中的数据

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 专栏交流&#x1f9e7;&…

picgo+gitee图床配置

node.js安装 刚开始顺着picgo操作,直接跳转到了node.js官网 下载的时候直接 Next,然后可以自定义安装路径,我的安装路径是C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Node.js 然后再在安装路径的根目录下新建两个文件夹,nodecache 和 nodeglobal, 如图所示:…

ida动态调试-cnblog

ida动态调试 传递启动ida服务 android_server在ida\dbgsrv目录中 adb push android_server /data/local/tmp/chmod 755 /data/local/tmp/android_server /data/local/tmp/android_serveradb forward tcp:23946 tcp:23946ida报错:大多是手机端口被占用 报错提示&#xff1a; …

GD32 MCU ADC采样率如何计算?

大家在使用ADC采样的时候是否计算过ADC的采样率&#xff0c;这个问题非常关键&#xff01; 以下为GD32F303系列MCU中有关ADC的参数&#xff0c;其中ADC时钟最大值为40MHz&#xff0c;12位分辨率下最大采样率为2.86MSPS.如果ADC时钟超频的话&#xff0c;可能会造成ADC采样异常&…

ARTS Week 36

unsetunsetAlgorithmunsetunset 本周的算法题为 1528. 重新排列字符串 给你一个字符串 s 和一个 长度相同 的整数数组 indices 。 请你重新排列字符串 s &#xff0c;其中第 i 个字符需要移动到 indices[i] 指示的位置。 返回重新排列后的字符串。 img 示例 1&#xff1a;输入&…

Google RichHF-18K 文本到图像生成中的丰富人类反馈

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

使用python编程的视频文件列表应用程序

简介&#xff1a; 在本篇博客中&#xff0c;我们将介绍一个基于 wxPython 的视频文件列表应用程序。该应用程序允许用户选择一个文件夹&#xff0c;并显示该文件夹中的视频文件列表。用户可以选择文件并查看其详细信息&#xff0c;导出文件列表为文本文件&#xff0c;以及播放…

Python酷库之旅-第三方库Pandas(008)

目录 一、用法精讲 16、pandas.DataFrame.to_json函数 16-1、语法 16-2、参数 16-3、功能 16-4、返回值 16-5、说明 16-6、用法 16-6-1、数据准备 16-6-2、代码示例 16-6-3、结果输出 17、pandas.read_html函数 17-1、语法 17-2、参数 17-3、功能 17-4、返回值…

68.WEB渗透测试-信息收集- WAF、框架组件识别(8)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;67.WEB渗透测试-信息收集- WAF、框架组件识别&#xff08;7&#xff09; 右边这些是waf的…

C++ | Leetcode C++题解之第217题存在重复元素

题目&#xff1a; 题解&#xff1a; class Solution { public:bool containsDuplicate(vector<int>& nums) {unordered_set<int> s;for (int x: nums) {if (s.find(x) ! s.end()) {return true;}s.insert(x);}return false;} };