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

基于mybatis-plus动态数据源实现mysql集群读写分离和从库负载均衡教程(详细案例)

基于mysql集群&mybatis-plus动态数据源实现数据库读写分离和从库负载均衡教程(详细案例)

本案例基于 Spring Boot 2.7.x + MyBatis-Plus 3.5.x + MySQL 8.0 实现,包含完整的项目搭建、配置、代码编写和测试验证,适合中小型项目快速落地读写分离。

一、环境准备

1. 数据库环境(已搭建的主从集群)

角色IP地址数据库账号权限说明
主库192.168.1.100root/Root@123456读写权限(处理写请求)
从库1192.168.1.101read_user/Read@123456只读权限(仅SELECT,处理读请求)
从库2192.168.1.102read_user/Read@123456只读权限(仅SELECT,处理读请求)

2. 项目依赖(pom.xml)

创建 Spring Boot 项目,在 pom.xml 中引入核心依赖(动态数据源、MyBatis-Plus、MySQL 驱动等):

<?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="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.15</version> <!-- 稳定版 --><relativePath/></parent><groupId>com.example</groupId><artifactId>mysql-read-write-split</artifactId><version>1.0-SNAPSHOT</version><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-boot-starter</artifactId><version>3.5.4.1</version></dependency><!-- 动态数据源插件(实现读写分离核心) --><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.6.1</version></dependency><!-- MySQL 驱动(适配 MySQL 8.0) --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version><scope>runtime</scope></dependency><!-- Lombok(简化实体类代码) --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- Spring Boot 测试(用于单元测试) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>

二、核心配置(application.yml)

src/main/resources/application.yml 中配置 多数据源、负载均衡策略、MyBatis-Plus 等核心参数:

# 服务端口
server:port: 8080# 数据库配置(动态数据源)
spring:datasource:dynamic:primary: master  # 默认数据源(写请求默认走主库)strict: false    # 非严格模式:找不到指定数据源时,自动使用primary数据源load-balance: round_robin  # 从库负载均衡策略:round_robin(轮询)、random(随机)health: true     # 开启数据源健康检测(故障从库自动剔除)health-timeout: 3000  # 健康检测超时时间(毫秒)# 多数据源列表datasource:# 主库数据源(处理写请求)master:url: jdbc:mysql://192.168.1.100:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=trueusername: rootpassword: Root@123456driver-class-name: com.mysql.cj.jdbc.Driver# 从库1数据源(处理读请求)slave1:url: jdbc:mysql://192.168.1.101:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=trueusername: read_userpassword: Read@123456driver-class-name: com.mysql.cj.jdbc.Driver# 从库2数据源(处理读请求)slave2:url: jdbc:mysql://192.168.1.202:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=trueusername: read_userpassword: Read@123456driver-class-name: com.mysql.cj.jdbc.Driver# 数据源分组(将从库归为一组,实现负载均衡)datasource-group:slave_group: slave1,slave2  # slave_group组包含slave1和slave2# MyBatis-Plus 配置
mybatis-plus:mapper-locations: classpath*:mapper/**/*.xml  # Mapper.xml文件路径type-aliases-package: com.example.entity       # 实体类包路径configuration:map-underscore-to-camel-case: true  # 开启下划线转驼峰(如user_name -> userName)log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL日志(方便测试验证)

三、代码编写(分层实现)

实体类 → Mapper → Service → Controller 分层编写代码,核心通过 @DS 注解指定数据源。

1. 实体类(Entity)

对应数据库 test 库的 user 表(提前在主库创建表):

package com.example.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.time.LocalDateTime;/*** 用户实体类(对应test.user表)*/
@Data
@TableName("user")  // 关联数据库表名
public class User {@TableId(type = IdType.AUTO)  // 主键自增private Long id;              // 用户IDprivate String username;      // 用户名private String password;      // 密码(实际项目需加密存储)private Integer age;          // 年龄private LocalDateTime createTime;  // 创建时间(默认当前时间)
}

2. 数据库表创建(主库执行)

在主库 test 库中创建 user 表(从库会自动同步):

CREATE TABLE IF NOT EXISTS `user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) NOT NULL COMMENT '密码',`age` int DEFAULT NULL COMMENT '年龄',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

3. Mapper 接口(数据访问层)

继承 MyBatis-Plus 的 BaseMapper,无需手动写 SQL(简单CRUD):

package com.example.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;/*** User Mapper 接口(MyBatis-Plus自动实现CRUD)*/
@Mapper  // 标记为MyBatis Mapper接口
public interface UserMapper extends BaseMapper<User> {// 无需额外方法,BaseMapper已提供:insert、deleteById、updateById、selectById、selectList等
}

4. Service 层(业务逻辑层)

核心层:通过 @DS 注解指定数据源,实现读写分离与负载均衡。

4.1 Service 接口
package com.example.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.example.entity.User;import java.util.List;/*** User Service 接口*/
public interface UserService extends IService<User> {// 写操作:新增用户(走主库)boolean addUser(User user);// 读操作:按ID查询用户(走从库集群,负载均衡)User getUserById(Long id);// 读操作:查询所有用户(走从库1,指定单个从库)List<User> listAllUserFromSlave1();// 读操作:按年龄查询用户(走从库2,指定单个从库)List<User> listUserByAge(Integer age);
}
4.2 Service 实现类(核心:@DS 注解使用)
package com.example.service.impl;import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Map;/*** User Service 实现类* 关键:@DS注解指定数据源,未指定则默认走primary(master)*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {/*** 写操作:新增用户* 未加@DS注解 → 默认走primary(master主库),符合"写请求走主库"原则*/@Overridepublic boolean addUser(User user) {return save(user);  // MyBatis-Plus提供的save方法(INSERT操作)}/*** 读操作:按ID查询用户* @DS("slave_group") → 走从库集群(slave1和slave2),按轮询策略负载均衡*/@Override@DS("slave_group")public User getUserById(Long id) {return getById(id);  // MyBatis-Plus提供的getById方法(SELECT操作)}/*** 读操作:查询所有用户* @DS("slave1") → 强制走slave1从库*/@Override@DS("slave1")public List<User> listAllUserFromSlave1() {return list();  // MyBatis-Plus提供的list方法(SELECT * FROM user)}/*** 读操作:按年龄查询用户* @DS("slave2") → 强制走slave2从库* 自定义SQL(需在Mapper.xml中编写)*/@Override@DS("slave2")public List<User> listUserByAge(Integer age) {// 调用Mapper自定义方法(参数用Map传递,或用@Param注解)return baseMapper.selectListByAge(age);}
}

5. Mapper 自定义 SQL(可选,复杂查询)

若需要复杂查询(如按条件筛选),在 src/main/resources/mapper/UserMapper.xml 中编写 SQL:

<?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.example.mapper.UserMapper"><!-- 自定义查询:按年龄查询用户 --><select id="selectListByAge" resultType="com.example.entity.User">SELECT id, username, password, age, create_time AS createTimeFROM userWHERE age = #{age}</select></mapper>

同时在 UserMapper 接口中添加对应方法:

// UserMapper.java 中新增方法
List<User> selectListByAge(@Param("age") Integer age);

6. Controller 层(接口层,用于测试)

编写 REST 接口,通过 Postman 或浏览器测试读写分离效果:

package com.example.controller;import com.example.entity.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** User 控制器(测试读写分离接口)*/
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;/*** 1. 写操作:新增用户(走主库)* 请求方式:POST* 请求体:{"username":"test1","password":"123456","age":20}*/@PostMapping("/add")public String addUser(@RequestBody User user) {boolean success = userService.addUser(user);return success ? "新增用户成功(走主库)" : "新增用户失败";}/*** 2. 读操作:按ID查询用户(走从库集群,负载均衡)* 请求方式:GET* 示例:http://localhost:8080/user/get/1*/@GetMapping("/get/{id}")public User getUserById(@PathVariable Long id) {User user = userService.getUserById(id);return user;}/*** 3. 读操作:查询所有用户(走slave1从库)* 请求方式:GET* 示例:http://localhost:8080/user/list/slave1*/@GetMapping("/list/slave1")public List<User> listAllUserFromSlave1() {return userService.listAllUserFromSlave1();}/*** 4. 读操作:按年龄查询用户(走slave2从库)* 请求方式:GET* 示例:http://localhost:8080/user/list/age?age=20*/@GetMapping("/list/age")public List<User> listUserByAge(@RequestParam Integer age) {return userService.listUserByAge(age);}
}

四、项目启动类

添加 @MapperScan 注解,扫描 Mapper 接口:

package com.example;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** 项目启动类*/
@SpringBootApplication
@MapperScan("com.example.mapper")  // 扫描Mapper接口所在包
public class MysqlReadWriteSplitApplication {public static void main(String[] args) {SpringApplication.run(MysqlReadWriteSplitApplication.class, args);}}

五、测试验证(核心步骤)

启动项目后,通过 SQL日志数据库数据 验证读写分离与负载均衡效果。

1. 验证“写操作走主库”

步骤1:调用新增用户接口

用 Postman 发送 POST 请求:

  • URL:http://localhost:8080/user/add
  • 请求体:
    {"username": "test_user1","password": "123456","age": 25
    }
    
步骤2:查看 SQL 日志

控制台会打印新增用户的 SQL,且数据源为 master

JDBC Connection [com.zaxxer.hikari.HikariProxyConnection@xxxx] will be used by dynamic datasource
==>  Preparing: INSERT INTO user ( username, password, age, create_time ) VALUES ( ?, ?, ?, ? )
==> Parameters: test_user1(String), 123456(String), 25(Integer), 2025-09-10T10:00:00(LocalDateTime)
<==  Updates: 1
步骤3:验证主从数据同步
  • 主库查询:SELECT * FROM test.user; → 能看到 test_user1
  • 从库1/2查询:SELECT * FROM test.user; → 也能看到 test_user1(主从同步生效)。

2. 验证“读操作走从库集群(负载均衡)”

步骤1:多次调用“按ID查询”接口

用浏览器或 Postman 多次访问:http://localhost:8080/user/get/1(假设新增用户的ID为1)。

步骤2:查看 SQL 日志(轮询效果)

第一次请求:数据源为 slave1

JDBC Connection [com.zaxxer.hikari.HikariProxyConnection@xxxx] will be used by dynamic datasource : slave1
==>  Preparing: SELECT id,username,password,age,create_time AS createTime FROM user WHERE id=?
==> Parameters: 1(Long)
<==  Total: 1

第二次请求:数据源自动切换为 slave2(轮询策略生效):

JDBC Connection [com.zaxxer.hikari.HikariProxyConnection@xxxx] will be used by dynamic datasource : slave2
==>  Preparing: SELECT id,username,password,age,create_time AS createTime FROM user WHERE id=?
==> Parameters: 1(Long)
<==  Total: 1

第三次请求:又切换回 slave1,以此循环,证明从库负载均衡生效。

3. 验证“指定从库读取”

步骤1:调用“查询所有用户(slave1)”接口

访问:http://localhost:8080/user/list/slave1,日志显示数据源为 slave1

JDBC Connection [com.zaxxer.hikari.HikariProxyConnection@xxxx] will be used by dynamic datasource : slave1
==>  Preparing: SELECT id,username,password,age,create_time AS createTime FROM user
==> Parameters: 
<==  Total: 1
步骤2:调用“按年龄查询(slave2)”接口

访问:http://localhost:8080/user/list/age?age=25,日志显示数据源为 slave2

JDBC Connection [com.zaxxer.hikari.HikariProxyConnection@xxxx] will be used by dynamic datasource : slave2
==>  Preparing: SELECT id, username, password, age, create_time AS createTime FROM user WHERE age = ?
==> Parameters: 25(Integer)
<==  Total: 1

六、常见问题处理

1. 从库连接失败(日志显示“Access denied”)

  • 原因:从库 read_user 账号密码错误,或未授予 SELECT 权限。
  • 解决:在从库执行授权语句:
    -- 从库执行:授予read_user只读权限
    GRANT SELECT ON test.* TO 'read_user'@'%' IDENTIFIED WITH mysql_native_password BY 'Read@123456';
    FLUSH PRIVILEGES;
    

2. 负载均衡不生效(始终走同一个从库)

  • 原因:application.yml 中未配置 datasource-group,或 @DS 注解指定的是单个从库(如 @DS("slave1"))。
  • 解决:确保 datasource-group 配置正确,且读操作注解为 @DS("slave_group")

3. 主从延迟导致读不到新数据

  • 原因:主库写入后,数据同步到从库有延迟(如1秒),立即读从库会看到旧数据。
  • 解决:核心读请求(如“刚新增完用户就查询”)强制走主库,添加 @DS("master") 注解:
    @Override
    @DS("master")  // 强制走主库,避免主从延迟
    public User getNewUserById(Long id) {return getById(id);
    }
    

七、总结

本案例通过 MyBatis-Plus 动态数据源插件 实现了:

  1. 读写分离:写操作默认走主库,读操作通过 @DS 注解走从库;
  2. 从库负载均衡:从库集群(slave_group)按轮询策略分发读请求;
  3. 灵活路由:支持指定单个从库(如 @DS("slave1"))或强制走主库(如 @DS("master"))。

该方案无需额外中间件,代码侵入性低,适合中小型 Spring Boot 项目快速落地读写分离,且后续可通过自定义负载均衡策略(如权重)进一步优化。


文章转载自:

http://sLlbW8Z8.hqwxm.cn
http://0kUo9WXR.hqwxm.cn
http://oq0iyrY7.hqwxm.cn
http://Q92LbbZV.hqwxm.cn
http://PXHiwWFI.hqwxm.cn
http://Ef0K9LON.hqwxm.cn
http://ROrVKnX4.hqwxm.cn
http://wMAwcnfE.hqwxm.cn
http://rFnYsMTb.hqwxm.cn
http://wXsyR2Ln.hqwxm.cn
http://nGXm1Tok.hqwxm.cn
http://YffO2Rly.hqwxm.cn
http://o5mEx23d.hqwxm.cn
http://uO00kDbV.hqwxm.cn
http://fl1cnxLg.hqwxm.cn
http://tLUtMNcl.hqwxm.cn
http://gEMmxx07.hqwxm.cn
http://8cIcO1oR.hqwxm.cn
http://MXORq7c6.hqwxm.cn
http://Q7Qs2W5o.hqwxm.cn
http://9oWJcr5A.hqwxm.cn
http://MKGQqms0.hqwxm.cn
http://oi2Vh4Uj.hqwxm.cn
http://nOUpWdbW.hqwxm.cn
http://GQMm47rR.hqwxm.cn
http://FMHOkaeL.hqwxm.cn
http://snk721tq.hqwxm.cn
http://3TxnCSeq.hqwxm.cn
http://zVsRbpfO.hqwxm.cn
http://EeJ3nEzZ.hqwxm.cn
http://www.dtcms.com/a/374633.html

相关文章:

  • Elasticsearch面试精讲 Day 14:数据写入与刷新机制
  • TDengine 选择函数 LAST_ROW() 用户手册
  • Flink 状态管理的核心能力
  • Hive实战(三)
  • git无法拉去远程仓库-connection reset
  • 计算机毕设推荐:基于Hadoop+Spark物联网网络安全数据分析系统 物联网威胁分析系统【源码+文档+调试】
  • 使用 BERT 实现意图理解和实体识别
  • QB/T 4674-2021 汽车内装饰用聚氨酯束状超细纤维合成革检测
  • spark11-sparkSQL 实现wordcount
  • 微硕双N-MOS管WST3392在汽车智能氛围灯系统中的应用
  • 小鹏汽车 vla 算法最新进展和模型结构细节
  • SpringBoot多场景中23种常用注解详解
  • 复杂PDF文档结构化提取全攻略——从OCR到大模型知识库构建
  • PySpark类库和Spark框架的比较
  • Sealos部署Rustdesk服务
  • 数据仓库详解
  • 网络编程---TCP
  • Tomcat商业部署调优(待完成)
  • GitHub SSH 连接超时解决方法 | 网络屏蔽了 GitHub 的 SSH 端口(22)
  • PyTorch自定义模型结构详解:从基础到高级实践
  • PythonSpark综合案例
  • 【Leetcode】高频SQL基础题--626.换座位
  • 字符串-14.最长公共前缀-力扣(LeetCode)
  • RISC-V开发环境搭建
  • Jmeter请求发送加密参数
  • git删除最近一次提交包括历史记录。
  • jmeter 带函数压测脚本
  • jmeter实现两个接口的同时并发
  • 在git仓库的空文件夹中添加.gitkeep文件
  • Vue3+Node.js 实现大文件上传:断点续传、秒传、分片上传完整教程(含源码)