当前位置: 首页 > news >正文

「Spring Boot + MyBatis-Plus + MySQL 一主两从」读写分离实战教程

🚀 Spring Boot + MyBatis-Plus + MySQL 一主两从多数据源实战(99% 场景覆盖)

在企业级开发中,数据库的读写分离几乎已成标配。本文基于 Spring Boot 3.3.12 + MyBatis-Plus + MySQL 构建一个一主两从动态多数据源读写分离项目,覆盖市面上 99% 的常见场景

🔧 技术栈:Spring Boot 3.3.12 + MyBatis-Plus 3.5.5 + dynamic-datasource 4.2.0 + MySQL + JDK17


🐳 MySQL 主从复制 Docker Compose 配置

点击这里查看 MySQL 主从复制 Docker Compose 配置


🧭 项目结构总览

springboot-mybatisplus-dynamic-ds/
├── pom.xml
├── README.md
├── src/
│ ├── main/
│ │ ├── java/com/example/demo/
│ │ │ ├── config/ 🛠️ 数据源、分页、全局配置
│ │ │ ├── controller/ 🎮 控制器
│ │ │ ├── domain/ 📦 实体类
│ │ │ ├── mapper/ 🧭 Mapper 接口
│ │ │ ├── service/ 💼 业务接口
│ │ │ ├── service/impl/ 💡 业务实现
│ │ │ └── SpringbootMybatisApp.java # 🚀 启动类
│ ├── resources/
│ │ ├── application.yml 📘 配置文件
│ │ └── mapper/ 🧾 Mapper XML 文件
└── …


📦 项目依赖(pom.xml)

<properties><java.version>17</java.version><spring.boot.version>3.3.12</spring.boot.version><mybatis-plus.version>3.5.5</mybatis-plus.version><dynamic.datasource.version>4.2.0</dynamic.datasource.version>
</properties><dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MyBatis-Plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!-- 多数据源插件 --><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot3-starter</artifactId><version>${dynamic.datasource.version}</version></dependency><!-- MySQL 驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- Lombok(可选) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>

🛠️ application.yml 多数据源配置

spring:datasource:dynamic:primary: masterstrict: falsedatasource:master:url: jdbc:mysql://mysql-master:3307/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: rootslave1:url: jdbc:mysql://mysql-slave1:3308/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: rootslave2:url: jdbc:mysql://mysql-slave2:3309/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: rootmybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

✅ 解释

配置项含义
primary: master默认使用主库(写库)
strict: false未标注数据源时不会报错,自动使用主库
datasource.master主库连接信息(写)
datasource.slave1从库1连接信息(读)
datasource.slave2从库2连接信息(读)
log-impl: StdOutImpl控制台打印SQL语句

点击这里查看高效管理Hosts文件的终极工具
在这里插入图片描述


⚙️ MyBatis-Plus 分页配置(可选)

📁 config/DataSourceAspect.java

package com.example.demo.aspect;import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class DataSourceAspect {@Before("execution(* com.example.demo..*.*(..))")public void before(JoinPoint point) {String ds = DynamicDataSourceContextHolder.peek();System.out.println("🔥 当前使用数据源:" + (ds != null ? ds : "master(默认)"));}
}

这段切面代码的作用是:在调用 com.example.demo 包下任意方法前,打印当前使用的数据源(若未指定则默认使用 master)。


📁 config/MybatisPlusConfig.java

package com.example.demo.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}

下面是对你这段 MybatisPlusConfig 配置类的详细解释:


MybatisPlusConfig.java 分页插件配置说明表

行号代码片段说明
1@Configuration声明这是一个 Spring 配置类,会被自动加载到 Spring 容器。
2public class MybatisPlusConfig自定义配置类,用于配置 MyBatis-Plus 的功能。
4@Bean将方法返回值交给 Spring 容器管理,作为一个 Bean 使用。
5public MybatisPlusInterceptor mybatisPlusInterceptor()定义并注册一个 MybatisPlusInterceptor 拦截器 Bean。
6MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();创建新的 MyBatis-Plus 插件拦截器(新版3.4+)。
8interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));添加分页插件,指定数据库类型为 MySQL。
它会拦截分页查询并自动添加 LIMIT 语句。
9return interceptor;返回配置完成的分页拦截器 Bean。

📌 分页插件功能补充说明

配置项类型示例值含义
DbType枚举DbType.MYSQL指定分页插件支持的数据库类型
setMaxLimitLong500L设置分页单页最大返回记录数(防止恶意超大页)
setOverflowbooleantrue页码溢出(如:页码 > 总页数)是否回到首页

🛠 使用示例代码

Page<User> page = new Page<>(1, 10); // 查询第1页,每页10条
Page<User> result = userMapper.selectPage(page, null);

MyBatis-Plus 会自动生成如下 SQL:

SELECT * FROM user LIMIT 0, 10;

📚 实体类 + Mapper + Service 示例

📁 domain/User.java

package com.example.demo.entity;import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;/*** 用户实体类*/
@Data
@TableName("user")
public class User {@TableId(type = IdType.AUTO)private Long id;private String name;private Integer age;
}

📁 mapper/UserMapper.java

package com.example.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {// 继承BaseMapper后,提供了丰富的 CRUD 方法
}

📁 service/UserService.java

package com.example.demo.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.User;import java.util.List;/*** 用户业务接口* <p>* 继承 MyBatis-Plus 的 IService<T>,自动拥有:* - CRUD 方法:save、updateById、removeById、getById 等* - 批量操作、分页查询、条件构造器支持*/
public interface UserService extends IService<User> {/*** 从从库 slave1 获取用户列表(示例切库)** @return 用户列表*/List<User> listFromSlave1();/*** 从从库 slave2 获取用户列表(示例切库)** @return 用户列表*/List<User> listFromSlave2();}

📁 service/impl/UserServiceImpl.java

package com.example.demo.service.impl;import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;import java.util.List;/*** 用户业务接口实现类* <p>* 默认所有数据库操作走 master 主库,可通过 @DS("slave1") / @DS("slave2") 注解指定从库*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {/*** 从从库 slave1 查询所有用户数据* @return 用户列表*/@Override@DS("slave1")public List<User> listFromSlave1() {return this.list();}/*** 从从库 slave2 查询所有用户数据* @return 用户列表*/@Override@DS("slave2")public List<User> listFromSlave2() {return this.list();}}

🎮 Controller 示例

📁 controller/UserController.java

package com.example.demo.controller;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/api/user")
public class UserController {@Autowiredprivate UserService userService;/*** 添加用户,不配置@DS,默认master*/@PostMapping("/add")public boolean add(@RequestBody User user) {return userService.save(user);}/*** 在 slave1数据库 查询所有用户*/@GetMapping("/slave1")public List<User> fromSlave1() {return userService.listFromSlave1();}/*** 在 slave2数据库 查询所有用户*/@GetMapping("/slave2")public List<User> fromSlave2() {return userService.listFromSlave2();}/*** 分页查询,不配置@DS,默认master*/@GetMapping("/page")public IPage<User> getPagedUsers(@RequestParam int page, @RequestParam int size) {return userService.page(Page.of(page, size));}
}

🌐 1. POST /users —— 新增用户(主库)

请求方式: POST

URL:

http://localhost:8080/api/user/add

请求体(JSON):

{"name": "张三","age": 28
}

curl 示例:

curl -X POST http://localhost:8080/users \-H "Content-Type: application/json" \-d '{"name":"张三","age":28}'

在这里插入图片描述


🌐 2. GET /users/slave1 —— 从从库1获取用户列表

请求方式: GET

URL:

http://localhost:8080/api/user/slave1

在这里插入图片描述


🌐 3. GET /users/slave2 —— 从从库2获取用户列表

请求方式: GET

URL:

http://localhost:8080/api/user/slave2

在这里插入图片描述


🌐 4. GET /users/page?page=1&size=5 —— 分页查询用户(主库)

请求方式: GET

URL:

http://localhost:8080/api/user/page?page=1&size=5

在这里插入图片描述


📖 MyBatis-Plus 常用方法全解析

方法说明示例
save(entity)插入一条记录userService.save(user);
saveBatch(Collection)批量插入userService.saveBatch(usersList);
getById(id)根据ID查询单条记录userService.getById(1L);
list()查询所有记录userService.list();
list(QueryWrapper)条件查询userService.list(new QueryWrapper<User>().eq("age", 20));
page(IPage)分页查询userService.page(new Page<>(1, 10));
updateById(entity)根据ID更新记录userService.updateById(user);
removeById(id)根据ID删除记录(逻辑删除)userService.removeById(1L);
remove(QueryWrapper)条件删除userService.remove(new QueryWrapper<User>().lt("age", 18));
selectCount(QueryWrapper)统计满足条件的记录数userMapper.selectCount(new QueryWrapper<User>().eq("age", 20));
乐观锁(@Version)自动维护版本号,避免并发冲突配合 updateById() 使用
逻辑删除(@TableLogic)自动过滤已逻辑删除数据查询默认不返回已删除数据

🧪 示例 SQL 表结构

CREATE TABLE user (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(64),age INT
);

分别在 master、slave1、slave2 初始化相同表结构,确保数据一致性。


📌 项目亮点总结

✅ 支持多数据源自动切换
✅ 支持 MyBatis-Plus 分页与条件构造器
✅ 使用注解即可切库,简洁无侵入
✅ 分层设计,业务清晰可拓展
✅ 适配 JDK17 + Spring Boot 3.3.x


❤️ 写在最后

👏 感谢阅读!

本项目为真实业务场景整理,涵盖读写分离 + 分页 + MyBatis-Plus CRUD 全面使用。建议搭配实际需求灵活扩展,如添加 Redis 缓存、权限框架等。

如果你觉得这篇文章对你有帮助,欢迎一键三连:点赞 👍、收藏 ⭐、评论 💬!

也欢迎关注我,第一时间获取更多 Spring Boot / 微服务实战案例 🚀

http://www.dtcms.com/a/306988.html

相关文章:

  • Linux 中,命令查看系统版本和内核信息
  • Linux 系统原理深度剖析与技术实践:从内核架构到前沿应用
  • 【选型】HK32L088 与 STM32F0/L0 系列 MCU 参数对比与选型建议(ST 原厂 vs 国产芯片)(单片机选型主要考虑的参数与因素)
  • 【python】列表“*”方式与推导式方式初始化区别
  • 数据结构——单链表1
  • 【WRF-Chem】EDGAR 排放数据处理:分部门合并转化为二进制(Python全代码)
  • RAG实战指南 Day 27:端到端评估框架实现
  • CSS-in-JS 动态主题切换与首屏渲染优化
  • 1.5.Vue v-for 和 指令修饰符
  • COZE 开源,新一代 AI Agent 本地部署一条龙
  • Excel文件解析
  • OpenWrt Network configuration
  • 百度统计在哪里添加网站?
  • Linux系统启动不受未挂载硬盘影响的解决方案
  • Windows系统使用命令生成文件夹下项目目录树(文件结构树)的两种高效方法
  • 深度学习-丢弃法 Dropout
  • C语言基础11——结构体1
  • Qt Quick 动画与过渡效果
  • QT中QTableView+Model+Delegate实现一个demo
  • TikTok 视频审核模型:用逻辑回归找出特殊类型的视频
  • 全栈:SSH和SSM和Springboot mybatisplus有什么区别?
  • 以ros的docker镜像为例,探讨docker镜像的使用
  • 力扣刷题日常(7-8)
  • 【Arch-Linux,hyprland】常用配置-已实验成功指令大全(自用)(持续更新)
  • 如何保证数据库的持久性与一致性:从 Linux 磁盘缓存策略到 MySQL 的设计
  • 执业药师证识别技术:医药健康生态中发挥愈发关键的作用
  • 微软:科技领域的创新巨头
  • Sleeping Cup 论坛:连接开发者与创新的桥梁
  • 隧道COVI检测器的用处
  • [SKE]使用OpenSSL库实现AES、SM4、DES、RSA、3DES_EDE和3DES_EEE算法的加解密验证