SpringBoot实现mysql多数据源配置(Springboot+Mybatis)

news2025/1/17 3:07:52

最近在学习SpringBoot的一些知识,主要是参考了纯洁的微笑的一些博客作为练手,想着他已经把入门的教程写的很详细了,如果再简单的把他的教程拷贝到自己的简书,这是是赤裸裸的剽窃,很不地道,作为一个技术人员,应该有一定的技术底线。所以,就把纯洁微笑的博客链接放在参考链接中的第一条,( ̄▽ ̄)"

目的

多数据源是指在同一个项目中写入/读取相同类型数据源的不同库的情况,比如在项目中需要访问mysql两个不同的数据库,常见于主从模式、或者数据库的分库的场景。
多数据源不包含不同类型数据源的情况,例如:一个项目中可能引入mysql数据库,同时也引入redis缓存数据库,这的确是两个数据源,但是类型不同,可以直接使用SpringBoot的相关配置即可完成。

开始动手干活吧

话不多说,直接开整

开发环境

  • IDE:idea
  • 项目构建:maven
  • 测试工具:postman

创建SpringBoot的项目

在这里说一下纯洁的微笑的项目结构,首先,创建了一个spring-boot-example的maven工程,该工程的打包类型为pom,每一个功能作为一个子module 项目,使用IDEA有一个比较高尴尬的事情,使用Spring 脚手架(spring initializr) 创建的module 在父项目的pom.xml文件中没有增加<module>标签。也有可能是因为我用的姿势不正确~
使用spring initalizr,在创建的时候,可以勾选所需要的依赖,避免手工填写相关的依赖,所选依赖如下图所示:

使用spring initalizr创建module

在spring-boot-example项目里创建一个新的module,名称为mybatis-mutidbsource。在父项目的pom.xml文件,手工增加 <module>mybatis-mutidbsource</module> ,如果不加的话,应该也没问题,这个没尝试过。迫于强迫症的习惯,还是手工加上了。

引入相关的依赖

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
  <mybatis.version>2.1.2</mybatis.version>
</properties>
<dependencies>
        <!-- web -->
        <!-- web 模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 测试相关 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置application.properties

在配置数据库信息时,需要注意一点,单数据源的数据库连接使用的参数为:spring.datasource.url ,具体示例如下:

spring.datasource.url = jdbc:mysql://localhost:3306/spring_boot_test?useSSL=true&serverTimezone=UTC

在使用多数据源的场景下,要将url修改为jdbc-url,对于多数据源参数的前缀信息也没有强制要求,但为了更方便的配置参数,建议相同数据源使用相同的前缀信息,修改后的配置参数如下:

spring.datasource.test1.jdbc-url = jdbc:mysql://localhost:3306/spring_boot_test1?useSSL=true&serverTimezone=UTC

其他的配置参数没有特别说明,完整的配置信息如下:

# 数据库相关
## 配置多数据源 test1
spring.datasource.test1.username=root
spring.datasource.test1.password=1qaz@WSX
spring.datasource.test1.driver-class-name=com.mysql.cj.jdbc.Driver
#此处url 修改为jdbc-url
spring.datasource.test1.jdbc-url = jdbc:mysql://localhost:3306/spring_boot_test1?useSSL=true&serverTimezone=UTC

## 配置多数据源 test2
spring.datasource.test2.username=root
spring.datasource.test2.password=1qaz@WSX
spring.datasource.test2.driver-class-name=com.mysql.cj.jdbc.Driver
#此处url 修改为jdbc-url
spring.datasource.test2.jdbc-url = jdbc:mysql://localhost:3306/spring_boot_test2?useSSL=true&serverTimezone=UTC


# mybatis
mybatis.type-aliases-package=com.fulin.example.bootexample.mybatismutidbsource.model

最最最最重要的数据源配置

第一个数据源

@Configuration
@MapperScan(basePackages = "com.fulin.example.bootexample.mybatismutidbsource.mapper.test1",sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSource1Config {

    @Bean(name = "test1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    @Primary
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mybatis/mapper/test1/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test1SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "test1TransactionManager")
    @Primary
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }


}

第二个数据源

@Configuration
@MapperScan(basePackages = "com.fulin.example.bootexample.mybatismutidbsource.mapper.test2",sqlSessionTemplateRef = "test2SqlSessionTemplate")
public class DataSource2Config {

    @Bean(name = "test2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test2")
    public DataSource testDataSource(){
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "test2SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mybatis/mapper/test2/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test2TransactionManager")
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "test2SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

比对两个数据源配置文件

使用文本比较软件,比对两个数据源配置文件,比对结果如下:


数据源配置文件差异比对

两个配置文件的主要差异有两点:

  1. 第一个数据源需要增加@Primary标签,用于区分是否为主库
  2. 两个数据源的bean 类型完全相同,应该使用名称进行区分

验证功能

为了能够验证配置的数据源,使用用户信息查询的简单demo验证配置是否正确。可以使用SpringBoot的Test类进行测试,也可以使用Restful接口的方式进行验证,在这里使用了后面的Restful接口的方式进行验证

创建model类

为了能够更加清晰的演示,针对不同的数据源,创建不同的model类,只是类名不同,具体字段等内容完全一致。使用了lombok的@Data标签可以省略getter和setter方法的编码。为了减少篇幅大小,在这里只显示了第一个数据源的model类。

@Data
public class User1Entity implements Serializable {

    private static final long serialVersionUID = 1L;
    public long id;
    public String userName;
    public String passWord;
    public UserSexEnum userSex;
    public String nickName;


    public User1Entity() {
        super();
    }

    public User1Entity(String userName, String passWord, UserSexEnum userSex) {
        super();
        this.passWord = passWord;
        this.userName = userName;
        this.userSex = userSex;
    }

    @Override
    public String toString() {
        return "userName: " + this.userName + ", pasword :" + this.passWord + ", sex :" + userSex.name();
    }
}

创建Mapper类

创建Mapper类,主要写了一些增删改查的方法,便于后面的测试。

@Mapper
@Repository
public interface User1Mapper {

    List<User1Entity> getAll();
    User1Entity getOne(Long id);
    void inserUser(User1Entity userEntity);

    void updateUser(User1Entity userEntity);

    void deleteById(Long id);

    User1Entity getOneByUserName(String userName);

    void deleteAll();
}

创建mapper.xml

创建mybatis的mapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.fulin.example.bootexample.mybatismutidbsource.mapper.test1.User1Mapper" >
    <resultMap id="BaseResultMap" type="com.fulin.example.bootexample.mybatismutidbsource.model.test1.User1Entity">
        <id column="id" property="id" jdbcType="BIGINT" />
        <result column="userName" property="userName" jdbcType="VARCHAR" />
        <result column="passWord" property="passWord" jdbcType="VARCHAR" />
        <result column="user_sex" property="userSex" javaType="com.fulin.example.bootexample.mybatismutidbsource.enums.UserSexEnum" />
        <result column="nick_name" property="nickName" jdbcType="VARCHAR" />
    </resultMap>

    <sql id="Base_Column_List">
        id , userName,passWord,user_sex,nick_name
    </sql>
    <select id="getAll" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from users
    </select>

    <select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from users
        where id = #{id}
    </select>
    <select id="getOneByUserName" parameterType="java.lang.String" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from users
        where userName = #{userName}
    </select>

    <insert id="inserUser" parameterType="com.fulin.example.bootexample.mybatismutidbsource.model.test1.User1Entity">
        insert into users (userName,passWord,nick_name,user_sex)
        values(#{userName},#{passWord},#{nickName},#{userSex})
    </insert>
    <select id="updateUser" parameterType="com.fulin.example.bootexample.mybatismutidbsource.model.test1.User1Entity">
        update users
        set
        <if test="userName != null"> userName = #{userName} , </if>
        <if test="passWord != null"> passWord = #{passWord} , </if>
        nick_name=#{nickName}
    </select>

    <delete id="deleteById" parameterType="java.lang.Long">
        delete from users
        where id = #{id}
    </delete>

    <delete id="deleteAll" >
        delete from users
    </delete>
</mapper>

controller

为了简单的测试相关的接口,没有编写service 层,直接在controller层调用dao层相关的接口。

@RestController
public class UserController {

    @Autowired
    private User1Mapper user1Mapper;

    @Autowired
    private User2Mapper user2Mapper;

    @GetMapping("/user1/getAllUser")
    public List<User1Entity> getAllUser1(){
        return user1Mapper.getAll();
    }

    @GetMapping("/user2/getAllUser")
    public List<User2Entity> getAllUser2(){
        return user2Mapper.getAll();
    }

    @GetMapping("/user1/{id}")
    public User1Entity getOneUser1(@PathVariable(value = "id") Long id){
        return user1Mapper.getOne(id);
    }

    @GetMapping("/user2/{id}")
    public User2Entity getOneUser2(@PathVariable(value = "id") Long id){
        return user2Mapper.getOne(id);
    }

    @PostMapping("/user1/inserUser1")
    public String insertUser1(@RequestParam(value = "userName") String userName,
                              @RequestParam(value = "passWord") String passWord,
                              @RequestParam(value = "userSex") String userSex){

        UserSexEnum  userSexEnum = UserSexEnum.valueOf(userSex);
        User1Entity user1Entity = new User1Entity( userName,  passWord,  userSexEnum);
        user1Mapper.inserUser(user1Entity);
        return "insert user ["+user1Entity+"] OK !";
    }

    @PostMapping("/user2/inserUser2")
    public String insertUser2(@RequestParam(value = "userName") String userName,
                              @RequestParam(value = "passWord") String passWord,
                              @RequestParam(value = "userSex") String userSex){

        UserSexEnum  userSexEnum = UserSexEnum.valueOf(userSex);
        User2Entity user2Entity = new User2Entity( userName,  passWord,  userSexEnum);
        user2Mapper.inserUser(user2Entity);
        return "insert user ["+user2Entity+"] OK !";
    }

}

项目结构

最后的项目结构:


项目结构

最后的测试

使用postman进行接口测试,在这里只展示insertUser的测试界面

postman调用一个数据源的insertUser接口
第一个数据库查询结果
postman调用一个数据源的insertUser接口
第二个数据库查询结果

问题列表

  1. 程序正常启动,调用系统接口时,提示“java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName”,具体错误信息如下:


    jdbcUrl is required with driverClassName 错误截图

    解决方法:在配置多数据源时,数据库连接串的关键字应该改为jdbc-url

  2. 在controller层引入mapper时,会提示"Could not autowire. No beans of 'User1Mapper' type found. "错误信息

    Could not autowire. No beans of 'User1Mapper' type found. 错误截图

    解决方法:在User1Mapper增加@Repository注解,这是一个相对比较简单的方法,其他修改方法可以参考文献3

参考链接

  1. Spring Boot(七):Mybatis 多数据源最简解决方案
  2. 纯洁的微笑源码 spring-boot-example
  3. Intellij IDEA中Mybatis Mapper自动注入警告的6种解决方案
最后编辑于:2024-10-04 14:46:28


喜欢的朋友记得点赞、收藏、关注哦!!!

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

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

相关文章

通过页面添加国际化数据,实现vue的国际化

element ui 写在前面1. 原有的vue的国际化处理1.1 语言文件1.2 lang的index.js1.3 入口文件导入1.3 应用 2. 通过页面添加国际化数据2.1 做法2.2 lang的index.js文件修改2.3 需要注意的点 总结写在最后 写在前面 需求&#xff1a;在系统的国际化管理页面添加国际化数据&#x…

机器视觉-相机、镜头、光源(总结)

目录 1、机器视觉光源概述 2、光源的作用 3、光谱 4、工业场景常见光源 4.1、白炽灯 4.2、卤素灯 4.3、 荧光灯 4.4、LED灯 4.5、激光灯 5、光源的基本性能 5.1、光通量 5.2、光效率 5.3、发光强度 5.4、光照度 5.5、均匀性 5.6、色温 5.7、显色性 6、基本光学…

嵌入式软开项目——电子手环开发——学习引导和资料网址

文末附所有资料下载链接 1、需要将该文件夹放置到英文路径下&#xff0c;否则无法运行 2、目录下总共四个文件夹 2.1 第一个文件夹“0、OV-Watch-main(原版)”&#xff0c;为开源代码的原版资料 2.2第二个文件夹” OV-Watch-main”&#xff0c;为本人对开源代码的学习&…

Java-I/O框架02:使用文件字节流复制文件

package com.yundait.Demo03;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream;/*** 使用文件字节流实现文件的复制* author zhang*/ public class FileInputStreamDemo03 {public static void main(String[] args) th…

多线程——线程的状态

线程状态的意义 ‌线程状态的意义在于描述线程在执行过程中的不同阶段和条件&#xff0c;帮助开发者更好地管理和调度线程资源。 线程的多种状态 线程的状态是一个枚举类型&#xff08;Thread.State&#xff09;&#xff0c;可以通过线程名.getState&#xff08;&#xff09…

基于Springboot+Vue 高考志愿咨询管理系统(源码+LW+部署讲解+数据库+ppt)

&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 会持续一直更新下去 有问必答 一键收藏关注不迷路 源码获取&#xff1a;https://pan.baidu.com/s/1aRpOv3f2sdtVYOogQjb8jg?pwdjf1d 提取码: jf1d &#…

react 总结+复习+应用加深

文章目录 一、React生命周期1. 挂载阶段&#xff08;Mounting&#xff09;补充2. 更新阶段&#xff08;Updating&#xff09;补充 static getDerivedStateFromProps 更新阶段应用补充 getSnapshotBeforeUpdate3. 卸载阶段&#xff08;Unmounting&#xff09; 二、React组件间的…

智能合约分享

智能合约练习 一、solidity初学者经典示例代码&#xff1a; 1.存储和检索数据&#xff1a; // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // 声明 Solidity 编译器版本// 定义一个名为 SimpleStorage 的合约 contract SimpleStorage {// 声明一个公共状态变量 d…

计算机网络:数据链路层 —— 虚拟局域网 VLAN

文章目录 局域网虚拟局域网 VLAN虚拟局域网 VLAN 概述实现机制IEEE 802.1Q帧以太网交换机的接口类型Access 接口Trunk 接口Hybrid 接口不进行人为的VLAN划分划分两个不同VLANTrunk接口去标签后进行转发Trunk接口直接转发 局域网 局域网&#xff08;Local Area Network&#xf…

工地安全新突破:AI视频监控提升巡检与防护水平

在建筑工地和其他劳动密集型行业&#xff0c;工人的安全一直是管理工作的重中之重。为了确保工地的安全管理更加高效和智能化&#xff0c;AI视频监控卫士。通过人工智能技术&#xff0c;系统不仅能实时监控&#xff0c;还能自动识别工地现场的安全隐患&#xff0c;为工地管理者…

HeterGCL 论文写作分析

HeterGCL 论文写作分析 这篇文章&#xff0c;由于理论证明较少&#xff0c;因此写作风格了polygcl是两种风格的。polygcl偏向理论的写作风格&#xff0c;而hetergcl就是实践派的风格 首先看标题&#xff0c;其的重点是Graph contrastive learning Framework。其重点是framewo…

标准日志插件项目【C/C++】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;项目日记_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 一&#xff0c;项目介…

HTML+CSS实现超酷超炫的3D立方体相册

效果演示 HTML和CSS实现一个简单的3D立方体加载动画的相册。它使用了HTML来构建立方体的结构&#xff0c;并通过CSS来添加样式和动画效果。 HTML <div class"loader3d"><div class"cube"><div class"face"><img src&qu…

LabVIEW偏振调制激光高精度测距系统

在航空航天、汽车制造、桥梁建筑等先进制造领域&#xff0c;许多大型零件的装配精度要求越来越高&#xff0c;传统的测距方法在面对大尺寸、高精度测量时&#xff0c;难以满足工业应用的要求。绝对测距技术在大尺度测量上往往会因受环境影响大、测距精度低而无法满足需求。基于…

社交媒体视频素材平台推荐

在内容创作日益重要的今天&#xff0c;社交媒体视频素材的需求不断增加。适合各种平台的视频素材不仅可以提升内容质量&#xff0c;还能吸引更多观众。以下是一些推荐的社交媒体视频素材平台&#xff0c;帮助你找到适合的资源。 蛙学网 蛙学网 是一个专注于社交媒体视频素材的平…

Sora高端制造业WordPress外贸主题

Sora是一款专为高端制造业设计的WordPress主题&#xff0c;由国内知名wordpress开发团队简站wordpress主题开发&#xff0c;它以红色为主色调&#xff0c;适合外贸企业出海建独立站的模板。这个主题适用于WordPress 6.0及以上版本&#xff0c;并且只服务于真正有需要的用户。主…

C++ | Leetcode C++题解之第504题七进制数

题目&#xff1a; 题解&#xff1a; class Solution { public:string convertToBase7(int num) {if (num 0) {return "0";}bool negative num < 0;num abs(num);string digits;while (num > 0) {digits.push_back(num % 7 0);num / 7;}if (negative) {dig…

论文阅读(二十六):Dual Attention Network for Scene Segmentation

文章目录 1.Introduction3.DANet3.1Position Attention Module3.2Channel Attention Module 论文&#xff1a;Dual Attention Network for Scene Segmentation   论文链接&#xff1a;Dual Attention Network for Scene Segmentation   代码链接&#xff1a;Github 1.Intr…

Vue3 学习笔记(五)Vue3 模板语法详解

在 Vue3 的世界里&#xff0c;模板语法是我们构建用户界面的基石。今天&#xff0c;让我们一起深入了解 Vue3 的模板语法&#xff0c;我将用通俗易懂的语言和实用的例子&#xff0c;带你掌握这项必备技能。 1、文本插值&#xff1a;最基础的开始 想在页面上显示数据&#xff1f…

深度学习模型入门教程:从基础到应用

深度学习模型入门教程&#xff1a;从基础到应用 前言 在人工智能的浪潮中&#xff0c;深度学习作为一种强大的技术&#xff0c;正在各行各业中发挥着越来越重要的作用。从图像识别到自然语言处理&#xff0c;深度学习正在改变我们的生活和工作方式。本文将带您深入了解深度学…