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

发布于:2025-07-31 ⋅ 阅读:(22) ⋅ 点赞:(0)

🚀 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: master
      strict: false
      datasource:
        master:
          url: jdbc:mysql://mysql-master:3307/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
          username: root
          password: root
        slave1:
          url: jdbc:mysql://mysql-slave1:3308/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
          username: root
          password: root
        slave2:
          url: jdbc:mysql://mysql-slave2:3309/testdb?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
          username: root
          password: root

mybatis-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 {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

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


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

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

📌 分页插件功能补充说明

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

🛠 使用示例代码

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 {

    @Autowired
    private 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 / 微服务实战案例 🚀


网站公告

今日签到

点亮在社区的每一天
去签到