Spring 多数据源搭建

news2024/11/18 7:50:22

目录

前言

正文 

1.Druid 介绍和使用 

2.其他多数据源解决方案 

总结


前言

对于复杂的业务和项目,可能在一个单体项目中存在需要连接多个数据库的情况。这时,就会使用到多数据源,实际中遇到的可能性比较大。


正文 

如果一个项目中需要连接 db1 ,同时一部分业务需要获取 db2 中的数据,则需要连接第二个数据源。这里一个数据源对应一个数据库,这两个数据库可能是部署在同一台服务器上,也可能不在同一台服务器上,通过配置 jdbc-url 来区别。

在配置上需要配置不同的 datasource 才能实现不同的数据源连接。

1.Druid 介绍和使用 

Druid 是阿里旗下开源的数据库连接池,提供强大的监控和扩展功能,包括数据库性能健康,获取 SQL 日志的能力。除此之外,也可以和 MyBaties 配合用于多数据源搭建。 

pom.xml依赖如下

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.23</version>
        </dependency>

为了测试多数据源,创建一个主库 test_spring_master,一个从库 test_spring_slave,创建 SQL 如下:

create database test_spring_master default character set utf8;
create database test_spring_slave default character set utf8;

配置文件内容如下:

spring:
  datasource:
    master: #主数据元
      username: master
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/test_spring_master?useTimezone=true&serverTimezone=UTC
      initialSize: 5
      minIdle: 5
      maxActive: 20
    slave: #第二个数据源
      username: slave
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/test_spring_slave?useTimezone=true&serverTimezone=UTC
      initialSize: 5
      minIdle: 5
      maxActive: 20

针对两个数据源配置,需要编写两个配置类,一个是主数据源配置类,另一个是从数据源配置类,分别命名为 MasterDataSourceConfig 和 SlaveDataSourceConfig,代码如下: 

package org.example.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = MasterDataSourceConfig.PACKAGE_NAME, sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {
    //定位到包类路径
    static final String PACKAGE_NAME = "org.example.mapper.master";
    //设置 mapper.xml位置。
    //不存在 *.xml 这种模糊匹配,必须准确的名称
    static final String MAPPER_LOCATION="classpath:org/example/mapper/master/MySinger.xml";
    @Value("${spring.datasource.master.jdbc-url}")
    private String url;
    @Value("${spring.datasource.master.username}")
    private String user;
    @Value("${spring.datasource.master.password}")
    private String password;
    @Value("${spring.datasource.master.driver-class-name}")
    private String driverClass;

    /**
     * 获得主数据源
     * @return
     */
    @Bean(name = "masterDataSource")
    public DataSource masterDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);//设置驱动
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 将 masterDataSource 注入到 masterTransactionManger
     * @return 管理数据库实物
     */
    @Bean(name = "masterTransactionManger")
    public DataSourceTransactionManager masterTransactionManger(){
        //使用了 @Bean,则可以直接依赖注入进去
        return new DataSourceTransactionManager(masterDataSource());
    }
    @Bean(name = "masterSqlSessionFactory")
    @Primary //如果有多个相同类型的Bean 优先使用本Bean
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource){
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(masterDataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResource(MasterDataSourceConfig.MAPPER_LOCATION));
        try {
            return sessionFactory.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

补充: 

  • @MapperScan 注解用于指定需要扫描的 Mapper 接口所在的包名,它通常与 @Configuration 注解一起使用,表示该注解所标注的类是一个配置类。basePackages 属性用于指定需要扫描的包名,多个包名可以用逗号隔开。例如,@MapperScan(basePackages = "com.example.mapper") 表示需要扫描 com.example.mapper 包以及其子包中的 Mapper 接口。
  • 此外,@MapperScan 注解还有一个 sqlSessionFactoryRef 属性,用于指定使用的 SqlSessionFactory 的名称,即在 Spring IoC 容器中定义的 SqlSessionFactory Bean 的名称。在多个数据源的情况下,可以通过该属性为不同的 Mapper 接口指定不同的 SqlSessionFactory,以使用不同的数据源。
  • 也就是说,@MapperScan 注解将指定包下的 MyBatis Mapper 接口与 SqlSessionFactory 关联起来。为了让 @MapperScan 注解知道要关联哪个 SqlSessionFactory 实例,需要通过 sqlSessionFactoryRef 属性指定 SqlSessionFactory 的 Bean 名称。

可以看到  masterSqlSessionFactory 方法上有 @Primary 注解,说明这时主库。而从数据源配置类和主数据源配置类比较类似,唯一不同就是一些参数和注解不同。

package org.example.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = SlaveDataSourceConfig.PACKAGE_NAME, sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDataSourceConfig {
    //定位到包类路径
    static final String PACKAGE_NAME = "org.example.mapper.slave";
    //设置 mapper.xml位置。
    static final String MAPPER_LOCATION="classpath:org/example/mapper/slave/MyStore.xml";
    @Value("${spring.datasource.slave.jdbc-url}")
    private String url;
    @Value("${spring.datasource.slave.username}")
    private String user;
    @Value("${spring.datasource.slave.password}")
    private String password;
    @Value("${spring.datasource.slave.driver-class-name}")
    private String driverClass;

    /**
     * 获得主数据源
     * @return
     */
    @Bean(name = "slaveDataSource")
    public DataSource slaveDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);//设置驱动
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 将 masterDataSource 注入到 masterTransactionManger
     * @return 管理数据库实物
     */
    @Bean(name = "slaveTransactionManager")
    public DataSourceTransactionManager slaveTransactionManager(){
        //使用了 @Bean,则可以直接依赖注入进去
        return new DataSourceTransactionManager(slaveDataSource());
    }
    @Bean(name = "slaveSqlSessionFactory")
    public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource slaveDataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(slaveDataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResource(SlaveDataSourceConfig.MAPPER_LOCATION));
        return sessionFactory.getObject();
    }

}

为了进一步实验,需要在两个库中分别创建表,于是主库创建 m_singers 歌手表,在从数据库中创建 m_stores 商店表。

use test_spring_master;
create table `m_singers`(
    `id` int(11) unsigned not null auto_increment comment '歌手主键',
    `name` varchar(80) not null comment '歌手名',
    `age` int(3) default null comment '年龄',
    primary key (`id`)
)engine InnoDB default character set=utf8;
use test_spring_slave;
create table `m_stores`(
    `id` int(11) unsigned not null  auto_increment comment '主键',
    `name` varchar(100) NOT NULL  comment '店名',
    `space` int(6) not null  comment '单位平方米',
    `description` varchar(300) not null comment '简介',
    primary key (`id`)
)engine InnoDB DEFAULT character set=utf8;

创建用户并授予权限

create  user 'master'@'localhost' identified by'123456';
show grants for 'master'@'localhost';
create  user 'slave'@'localhost' identified by'123456';
GRANT ALL PRIVILEGES ON test_spring_slave.m_stores TO 'slave'@'localhost';
GRANT ALL PRIVILEGES ON test_spring_master.m_singers TO 'master'@'localhost';

 继续分别创建对应的实体类 

MyStore.java

package org.example.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class MyStore implements Serializable {
    // 主键
    private Integer id;
    // 名称
    private String name;
    // 地区
    private int space;
    // 描述
    private String description;
}

MySinger.java 

package org.example.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class MySinger implements Serializable {

    private Integer id;

    private String name;

    private int age;
}

对应的 Dao 文件:

MyStore。 

package org.example.mapper.slave;


import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.example.entity.MyStore;

@Mapper
public interface MyStoreDao {

    int insert(MyStore record);

    int updateByPrimaryKey(MyStore record);

    MyStore findByName(@Param("name") String name);
}

MySinger。 

package org.example.mapper.master;


import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.example.entity.MySinger;

@Mapper
public interface MySingerDao {

    int deleteByPrimaryKey(Integer id);

    int insert(MySinger record);

    int insertSelective(MySinger record);

    MySinger selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(MySinger record);

    int updateByPrimaryKey(MySinger record);

    MySinger findByName(@Param("name") String name);
}

剩下的 controller,service 部分基本就是一般流程了,就不继续向下写了。 最后自己进行测试:

完成 。

2.其他多数据源解决方案 

其他解决方案也很多,如使用多个 Bean、自实现动态 DataSource 等,其中有一种方法非常简单高效 ,就是使用第三方的依赖包,动态加载不同的数据源,推荐使用 dynamic-datasource-spring-boot-starter。

dynamic-datasource-spring-boot-starter 是国内开发者维护的多数据源解决方案插件被作者称为一个基于 springboot 的快速继承多数据源的启动器。它的优点主要有支持数据源分组,适用于多种场景,纯粹多库、读写分离、一主多从混合模式。

POM.xml依赖

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

数据源配置

spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据数组,默认只即为 master
      strict: false #设置严格模式,默认false不启动。启动后在未匹配到指定数据源时会抛出异常,不启动则使用默认数据源。
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver #3.2.0开始支持 SPI 可省略此配置
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_2:
          url: ENC(xxxxx) #内置加密,使用详情请看文档。
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: com.mysql.cj.jdbc.Driver
          scherma: db/schema.sql #配置生效,自动初始化表格
          data: db/data.sql #配置生效,自动初始化数据
          continue-on-error: true #默认true ,初始化失败是否
          separator: "," #sql 默认分隔符
          #这些配置会配置一个默认库 master, 一个组 slave 下有两个子库 slave_1, slave_2

配置解释:

在这段配置中:

spring.datasource.dynamic 是 dynamic-datasource-spring-boot-starter 的配置前缀。

  • primary: master 设置主数据源为 “master”。

  • strict: false 为非严格模式。如果请求的数据源不存在,系统会自动回退到主数据源,而不是抛出异常。

  • datasource: 下面列出了所有的数据源,每一个数据源都有一个名字(在这个例子中是 master, slave_1, slave_2),和对应的数据源详情(url, username, password, driver-class-name)。

每个具体的数据源(比如 “master”)下:

  • url: 数据库的 JDBC URL。

  • username: 数据库用户名。

  • password: 数据库密码。ENC(xxx) 是一个加密格式,能够保护你的敏感信息不被明文展示。

  • driver-class-name: JDBC 驱动类型。如果你依赖的版本在3.2.0以上,驱动可以被自动识别,这个配置也可以省略。

scherma: db/schema.sql 和 data: db/data.sql 是你自定义的SQL语句文件。在 Spring Boot 中可以通过这种方式定义 SQL 文件的路径,然后在应用启动的时候自动执行这些 SQL 文件。

强调一下,这只是个示例,你需要根据具体的数据库信息(如类型、地址、端口、用户名、密码等)去做相应的调整和修改。例如,替换 xx.xx.xx.xx 为实际的数据库服务器地址,并且替换 username 和 password 为正确的数据库访问凭证等。

最后使用 @DS 注解,采取就近原则,方法上的注解优先于类上的注解,代码如下 : 

package org.example.service;

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;

import java.util.List;

@Service
@DS("slave")
public class UserServiceImpl implements UserService{
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public List selectAll(){
        return jdbcTemplate.queryForList("select * from user");
    }
    @Override
    @DS("slave_1")
    public List selectByCondition(){
        return jdbcTemplate.queryForList("select * from user where age > 10");
    }
    
}


总结

多数据源搭建提供了灵活性、性能优化、隔离性和安全性方面的优势。它使应用程序能够更好地适应不同的需求,并在处理数据时提供更好的性能和可扩展性。

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

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

相关文章

Unity Image - 镜像

1、为什么要使用镜像 在游戏开发过程中&#xff0c;我们经常会为了节省 美术图片资源大小&#xff0c;美术会将两边相同的图片进行切一半来处理。如下所示一个按钮 需要 400 * 236&#xff0c;然而美术只需要切一张 74*236的大小就可以了。这样一来图集就可以容纳更多的图片。…

Centos7使用阿里云镜像加速服务安装Docker

文章目录 一、前提说明二、安装docker1、创建docker文件夹2、安装所需的软件包3、设置Docker仓库4、安装docker5、启动验证使用阿里云镜像加速服务 三、卸载docker 一、前提说明 需要先安装好虚拟机&#xff0c;可以查看这篇https://blog.csdn.net/qq_36433289/article/detail…

Docker的常用基本命令(基础命令)

文章目录 1. Docker简介2. Docker环境安装Linux安装 3. 配置镜像加速4. Docker镜像常用命令列出镜像列表搜索镜像下载镜像查看镜像版本删除镜像构建镜像推送镜像 5. Docker容器常用命令新建并启动容器列出容器停止容器启动容器进入容器删除容器&#xff08;慎用&#xff09;查看…

Java-easyExcel入门教程

文章目录 前言一、简介二、使用步骤1. 引入依赖2. 前提准备3. 实现导出4. 实现导入 三、我所遇到的问题四、总结 前言 在日常开发中经常会遇到一些 excel 表导入导出的需求&#xff0c;以往会使用 POI 封装成工具类来处理这些导入导出的需求&#xff0c;但是 POI 在导入大文件…

WSL2+tensorflow-gpu 2.3.0 C++ 源码编译

wsl2已有gcc 版本为9.4.0&#xff0c;但tensorflow2.3.0需对应gcc7.3.1 tensorflow与cuda cudnn python bazel gcc版本对应关系 故需下载一个低版本的gcc&#xff0c;但同时还想保留较高版本的gcc&#xff0c;那么参考文章&#xff1a;深度学习环境搭建(二): Ubuntu不同版本g…

【笔记】2023最新Python安装教程(Windows 11)

&#x1f388;欢迎加群交流&#xff08;备注&#xff1a;csdn&#xff09;&#x1f388; ✨✨✨https://ling71.cn/hmf.jpg✨✨✨ &#x1f913;前言 作为一名经验丰富的CV工程师&#xff0c;今天我将带大家在全新的Windows 11系统上安装Python。无论你是编程新手还是老手&…

HBase安装配置:一键自动安装配置

使用shell脚本一键下载、安装、配置HBase&#xff08;单机版&#xff09; 1. 把下面的脚本复制保存为/tmp/install_hbase.sh文件 #!/bin/bash# 安装之前确保安装目录有写入权限&#xff0c;若没有&#xff0c;自行增加 # 安装版本 zk_version"2.4.8" # 安装目录 zk…

5G承载网和大客户承载的演进

文章目录 移动4/5G承载网联通和电信4/5G承载网M-OTN&#xff08;Metro-optimized OTN&#xff09;&#xff0c;城域型光传送网PeOTN&#xff08;packet enhanced optical transport network&#xff09;&#xff0c;分组增强型OTN板卡增强型PeOTN集中交叉型PeOTN VC-OTN&#x…

如何从 Android 手机恢复已删除的视频

您是否曾经丢失过手机中的任何数据&#xff1f;如今&#xff0c;由于 Android 上的应用程序崩溃、根进程停止、Android 更新失败等等&#xff0c;数据丢失很普遍。错误删除是丢失视频、录音和音乐副本的另一种可能的方式。 丢失包含有关新完成的项目的重要信息的视频或婚礼、周…

Day47力扣打卡

打卡记录 多边形三角剖分的最低得分&#xff08;区间DP&#xff09; 链接 class Solution:def minScoreTriangulation(self, values: List[int]) -> int:n len(values)f [[0] * n for _ in range(n)]for i in range(n - 3, -1, -1):for j in range(i 2, n):f[i][j] mi…

Linux 系统是如何收发网络包的?

一、Linux 网络协议栈 如下是TCP/IP四层网络模型&#xff0c;实际上Linux 网络协议栈与它相似 下图是Linux 网络协议栈 二、Linux 接收网络包的流程 1.网卡是计算机里的一个硬件&#xff0c;专门负责接收和发送网络包&#xff0c;当网卡接收到一个网络包后&#xff0c;会通过…

如何快速了解一家公司?

在炒股过程中&#xff0c;我们想要了解一家公司是否具有投资价值&#xff0c;需要查看和阅读很多公司的相关资料。股民们自行去查询往往会花费很多的时间精力&#xff0c;所以专业的炒股软件一般都会给股民提供这些现成的资料。 在金斗云智投APP内&#xff0c;进入到个股详情页…

SCA技术进阶系列(四):DSDX SBOM供应链安全应用实践

一、SBOM的发展趋势 数字时代&#xff0c;软件已经成为维持生产生活正常运行的必备要素之一。随着容器、中间件、微服务、 DevOps等技术理念的演进&#xff0c;软件行业快速发展&#xff0c;但同时带来软件设计开发复杂度不断提升&#xff0c;软件供应链愈发复杂&#xff0c;软…

28.线段树与树状数组基础

一、线段树 1.区间问题 线段树是一种在算法竞赛中常用来维护区间的数据结构。它思想非常简单&#xff0c;就是借助二叉树的结构进行分治&#xff0c;但它的功能却非常强大&#xff0c;因此在很多类型的题目中都有它的变种&#xff0c;很多题目都需要以线段树为基础进行发展。…

Windows server 2016 FTP服务器的搭建

FTP&#xff08;File Transfer Protocol&#xff09;是一个用来在两台计算机之间传输文件的通信协议。这两台计算机中&#xff0c;一台是FTP服务器&#xff0c;另一台是FTP 客户端。 1.安装FTP服务与建立FTP站点 1.1 打开服务器管理器——单击仪表盘的添加角色和功能 1.2 持续…

Gavin Wood:财库保守主义偏离了初心,应探索 Fellowship 等更有效的资金部署机制

波卡创始人 Gavin Wood 博士最近接受了 The Kusamarian 的采访&#xff0c;分享了他的过往经历、对治理的看法&#xff0c;还聊到了 AI、以太坊、女巫攻击、财库等话题。本文整理自 PolkaWorld 对专访编译的部分内容&#xff0c;主要包含了 Gavin 对治理、财库提案、生态资金分…

听GPT 讲Rust源代码--src/tools(4)

题图由AI生成 File: rust/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs 在Rust源代码中&#xff0c;rust/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs这个文件是rust-analyzer工具的一部分&#xff0c;它定义了用于将类型系统中的实体进行唯一标识和共享…

15.oracle的 listagg() WITHIN GROUP () 行转列函数使用

1.使用条件查询 查询部门为20的员工列表 -- 查询部门为20的员工列表 SELECT t.DEPTNO,t.ENAME FROM SCOTT.EMP t where t.DEPTNO 20 ; 效果&#xff1a; 2.使用 listagg() WITHIN GROUP () 将多行合并成一行(比较常用) SELECT T .DEPTNO, listagg (T .ENAME, ,) WIT…

uniapp小程序分包页面引入wxcomponents(vue.config.js、copy-webpack-plugin)

实例&#xff1a;小程序添加一个源生小程序插件&#xff0c;按照uniapp官方的说明&#xff0c;要放在wxcomponents。后来发现小程序超2m上传不了。 正常的编译情况 会被编译到主包下 思路&#xff1a;把wxcomponents给编译到分包sub_package下 用uniapp的vue.config.js自定义…

【报名】2023产业区块链生态日暨 FISCO BCOS 开源六周年生态大会

作为2023深圳国际金融科技节系列活动之一&#xff0c;由深圳市地方金融监督管理局指导&#xff0c;微众银行、金链盟主办的“2023产业区块链生态日暨FISCO BCOS开源六周年生态大会”将于12月15日下午14:00在深圳举办。 今年的盛会将进一步升级&#xff0c;以“FISCO BCOS和TA的…