Spring Boot 3 + MyBatis-Plus 高性能持久层开发实战:从入门到调优
在 Java 后端开发中,持久层是连接业务逻辑与数据库的核心纽带,其性能与编码效率直接决定了整个系统的上限。Spring Boot 3 作为主流的微服务开发框架,凭借自动配置、简化部署等特性极大降低了开发成本;而 MyBatis-Plus(简称 MP)则在 MyBatis 的基础上,提供了强大的 CRUD 封装、分页插件、条件构造器等功能,彻底解放了开发者手写 SQL 的负担。
本文将从实战角度出发,深度解析 Spring Boot 3 与 MyBatis-Plus 的整合流程,结合丰富的代码案例,讲解核心功能的使用技巧,并针对持久层常见的性能瓶颈(如分页效率、SQL 优化、缓存设计)提供解决方案,帮助开发者构建高效、健壮的持久层架构。
一、环境准备与基础整合
1.1 环境依赖
首先搭建 Spring Boot 3 + MyBatis-Plus 基础环境,选用 MySQL 8.0 作为数据库(适配 Spring Boot 3 的兼容性),引入核心依赖(使用 Maven 管理)。
xml
> <groupId>org.springframework.boot<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.</version>
<relativePath/>
<dependencies<!-- Spring Boot Web 依赖(可选,用于接口测试) -->
<dependency>
<groupId>org.springframework.boot<artifactId>spring-boot-starter-web</artifactId>
<!-- MyBatis-Plus 核心<dependency<groupId>com.baomidou<artifactId>mybatis-plus-boot-st</artifactId>
<version>3.5</version</dependency><!-- MySQL 驱动(适配 MySQL 8.0) -->
<groupId</groupId<artifactId></artifactId<scope></scope>
<!-- lombok 简化实体类(可选,推荐) --><dependency>
<groupId>org.projectl</groupId>
<artifactId>lombok<optional</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot<artifactId>spring-boot-starter-test</artifactId>
</scope></dependency>
<!-- 分页插件依赖(MyBatis-Plus 内置,需手动配置<dependency<groupId>com.baomidou<artifactId>mybatis-plus-extension</artifactId>
<version>3.5.</version></dependency>
</dependencies>
1.2 配置文件编写
在 application.yml 中配置数据库连接信息、MyBatis-Plus 核心参数(如 mapper 扫描路径、日志打印、驼峰命名映射等),确保框架能够正常工作。
yaml
spring:
# 数据库配置
datasource:
url: jdbc:mysql://localhost:3306/mp_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Plus 配置
mybatis-plus:
# 扫描 mapper.xml 文件(如果使用 XML 编写 SQL)
mapper-locations: classpath:mapper/**/*.xml
# 扫描实体类包(用于别名配置,可选)
type-aliases-package: com.example.mpdemo.entity
# 开启驼峰命名映射(数据库下划线命名 → Java 驼峰命名)
configuration:
map-underscore-to-camel-case: true
# 打印 SQL 日志(开发环境开启,生产环境关闭)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 全局配置(可选,统一配置主键生成策略等)
global-config:
db-config:
# 主键生成策略:自增(MySQL 适用),可选 AUTO、INPUT、ASSIGN_ID 等
id-type: auto
1.3 核心配置类
编写 MyBatis-Plus 配置类,注册分页插件、乐观锁插件等核心组件(Spring Boot 3 中需使用 @Configuration 注解,替代传统的 XML 配置)。
java
package com.example.mpdemo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus 核心配置类
* 注册分页插件、乐观锁插件等
*/
@Configuration
// 扫描 mapper 接口包(必须配置,否则 mapper 接口无法被 Spring 管理)
@MapperScan("com.example.mpdemo.mapper")
public class MyBatisPlusConfig {
/**
* 注册 MyBatis-Plus 拦截器(分页、乐观锁等功能依赖此拦截器)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1. 分页插件(指定数据库类型为 MySQL)
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 2. 乐观锁插件(用于解决并发更新冲突)
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
二、核心功能实战(附代码案例)
本节将结合实际业务场景(以「用户管理」为例),讲解 MyBatis-Plus 的核心功能,包括实体类设计、Mapper 接口编写、CRUD 操作、条件构造器、分页查询等,所有案例均可直接复制到项目中使用。
2.1 实体类设计(Entity)
使用 Lombok 的 @Data 注解简化实体类编写,结合 MyBatis-Plus 的注解配置数据库表、字段映射关系、主键策略等。
java
package com.example.mpdemo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户实体类(对应数据库表:sys_user)
*/
@Data
// 指定数据库表名(如果实体类名与表名一致,可省略此注解)
@TableName("sys_user")
public class SysUser {
/**
* 主键 ID(自增策略,对应数据库主键自增)
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名(非空,唯一)
*/
@TableField("username")
private String username;
/**
* 密码(加密存储)
*/
private String password;
/**
* 昵称
*/
private String nickname;
/**
* 性别(0:女,1:男,2:未知)
*/
private Integer gender;
/**
* 手机号
*/
private String phone;
/**
* 状态(0:禁用,1:正常)
*/
private Integer status;
/**
* 创建时间(自动填充)
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间(自动填充)
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除标识(0:未删除,1:已删除)
* MyBatis-Plus 自动过滤已删除数据
*/
@TableLogic
private Integer isDeleted;
/**
* 版本号(用于乐观锁,每次更新自动递增)
*/
@Version
private Integer version;
}
关键注解说明:
- @TableName:指定实体类对应的数据表名,解决「实体类名与表名不一致」问题。
- @TableId:指定主键字段,type 属性配置主键生成策略(AUTO 自增、ASSIGN_ID 雪花算法等)。
- @TableField:指定实体类字段与数据库字段的映射关系,可省略(默认驼峰命名映射)。
- @TableLogic:标记逻辑删除字段,MyBatis-Plus 会自动在查询、更新时添加「is_deleted = 0」条件。
- @Version:标记乐观锁版本字段,并发更新时会自动校验版本号,避免数据冲突。
- @TableField(fill = ...):配置字段自动填充,需配合「元对象处理器」使用(下文讲解)。
2.2 元对象处理器(自动填充字段)
对于 createTime、updateTime 等公共字段,无需手动设置值,通过 MyBatis-Plus 的元对象处理器(MetaObjectHandler)实现自动填充。
java
package com.example.mpdemo.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 元对象处理器:实现公共字段自动填充
*/
@Component // 必须交给 Spring 管理
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入操作时,自动填充 createTime 和 updateTime
*/
@Override
public void insertFill(MetaObject metaObject) {
// 填充 createTime(字段名:createTime,值:当前时间)
strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// 填充 updateTime
strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
// 可选:填充默认状态(如 status = 1,isDeleted = 0)
strictInsertFill(metaObject, "status", Integer.class, 1);
strictInsertFill(metaObject, "isDeleted", Integer.class, 0);
strictInsertFill(metaObject, "version", Integer.class, 1);
}
/**
* 更新操作时,自动填充 updateTime
*/
@Override
public void updateFill(MetaObject metaObject) {
strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
2.3 Mapper 接口编写
MyBatis-Plus 提供了 BaseMapper 接口,封装了所有基础 CRUD 操作,开发者只需让自定义 Mapper 继承 BaseMapper,即可无需编写任何方法,直接使用 CRUD 功能。
java
package com.example.mpdemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户 Mapper 接口
* 继承 BaseMapper,获得 MyBatis-Plus 内置的 CRUD 方法
*/
@Mapper // 或在配置类中使用 @MapperScan 扫描,二选一
public interface SysUser<SysUser> {
// 基础 CRUD 方法无需编写,BaseMapper 已封装
// 如需自定义 SQL,可在此处编写方法(配合 XML 或注解)
/**
* 自定义查询:根据用户名查询用户(注解方式编写 SQL)
*/
@Select("SELECT * FROM sys_user WHERE username = #{username} AND is_deleted = 0")
SysUser selectByUsername(@Param("username") String username);
/**
* 自定义查询:根据角色 ID 查询用户列表(XML 方式编写 SQL,见下方)
*/
<SysUser> selectUserByRoleId(@Param("roleId") Long roleId);
}
XML 方式编写自定义 SQL(可选)
如果自定义 SQL 较为复杂,推荐使用 XML 方式编写(更易维护),在 resources/mapper 目录下创建 SysUserMapper.xml 文件。
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mpdemo.mapper.SysUserMapper">
<!-- 自定义查询:根据角色 ID 查询用户列表<select id="selectUserByRoleId" resultType="com.example.mpdemo.entity.SysUser">
SELECT u.*
FROM sys_user u
LEFT JOIN sys_user_role ur ON u.id = ur.user_id
WHERE ur.role_id = #{roleId}
AND u.is_deleted = 0
ORDER BY u.create</select<!-- 自定义更新:批量更新用户状态<update id="updateUserStatusBatch">
UPDATE sys_user
SET status = #{status}
WHERE id IN<foreach collection="ids" item="id" open="(" separator="," close=")">
#{</foreach>
AND is_deleted = 0
</mapper>
2.4 Service 层封装(可选,推荐)
MyBatis-Plus 提供了 IService 接口和 ServiceImpl 实现类,进一步封装了 Mapper 层的操作,支持批量操作、事务管理等功能,推荐在实际开发中使用 Service 层隔离持久层与业务层。
java
// 1. Service 接口(继承 IService)
package com.example.mpdemo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mpdemo.entity.SysUser;
import java.util.List;
public interface SysUserService extends<SysUser> {
// 基础 CRUD 方法无需编写,IService 已封装
// 自定义业务方法
SysUser getByUsername(String username);
<SysUser> getUserListByRoleId(Long roleId);
boolean<Long> ids, Integer status);
}
// 2. Service 实现类(继承 ServiceImpl,实现自定义接口)
package com.example.mpdemo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mpdemo.entity.SysUser;
import com.example.mpdemo.mapper.SysUserMapper;
import com.example.mpdemo.service.SysUserService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
// 注入 Mapper(可选,ServiceImpl 已自动注入)
private final SysUserMapper sysUserMapper;
public SysUserServiceImpl(SysUserMapper sysUserMapper) {
this.sysUserMapper = sysUserMapper;
}
@Override
public SysUser getByUsername(String username) {
return sysUserMapper.selectByUsername(username);
}
@Override
public List<SysUser> getUserListByRoleId(Long roleId) {
return sysUserMapper.selectUserByRoleId(roleId);
}
@Override
public boolean updateStatusBatch<Long> ids, Integer status) {
// 调用自定义 XML 中的方法
return sysUserMapper.updateUserStatusBatch(ids, status) > 0;
}
}
2.5 核心 CRUD 操作实战(测试案例)
以下通过 Spring Boot 测试类,演示 Mapper 层和 Service 层的核心 CRUD 操作,所有案例均可直接运行。
java
package com.example.mpdemo;
import com.example.mpdemo.entity.SysUser;
import com.example.mpdemo.service.SysUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class SysUserTest {
@Autowired
private SysUserService sysUserService;
// 1. 新增用户(自动填充 createTime、updateTime 等字段)
@Test
public void testInsert() {
SysUser user = new SysUser();
user.setUsername("zhangsan");
user.setPassword("123456"); // 实际开发中需加密(如 BCrypt)
user.setNickname("张三");
user.setGender(1);
user.setPhone("13800138000");
// 无需设置 createTime、updateTime、status 等字段(自动填充)
boolean success = sysUserService.save(user);
System.out.println("新增结果:" + success + ",新增用户 ID:" + user.getId());
}
// 2. 批量新增用户
@Test
public void testInsertBatch() {
<SysUser> user<>();
for (int i<= 5; i++) {
SysUser user = new SysUser();
user.setUsername("user" + i);
user.setPassword("123456");
user.setNickname("用户" + i);
user.setGender(i % 2);
user.setPhone("1380013800" + i);
userList.add(user);
}
// 批量新增(效率高于循环单条新增)
boolean success = sysUserService.saveBatch(userList);
System.out.println("批量新增结果:" + success);
}
// 3. 根据 ID 查询用户
@Test
public void testSelectById() {
SysUser user = sysUserService.getById(1L);
System.out.println("查询到的用户:" + user);
}
// 4. 根据条件查询用户列表(使用条件构造器 QueryWrapper)
@Test
public void testSelectByCondition() {
// 条件:性别为男(gender = 1)、状态正常(status = 1)、昵称包含「张」
Query<SysUser> query<>();
queryWrapper.eq("gender", 1) // 等于
.eq("status", 1)
.like("nickname", "张") // 模糊查询
.orderByDesc("create_time"); // 按创建时间降序
List<SysUser> userList = sysUserService.list(queryWrapper);
System.out.println("符合条件的用户列表:" + userList);
}
// 5. 分页查询用户(核心功能,解决大数据量查询性能问题)
@Test
public void testSelectPage() {
// 分页参数:第 1 页,每页 10 条数据
Page<SysUser> page =<>(1, 10);
// 条件:状态正常(status = 1)
<SysUser> queryWrapper = new Query<>();
queryWrapper.eq("status", 1);
// 分页查询(返回分页结果,包含总条数、总页数、当前页数据等<SysUser> userPage = sysUserService.page(page, queryWrapper);
System.out.println("总条数:" + userPage.getTotal());
System.out.println("总页数:" + userPage.getPages());
System.out.println("当前页数据:" + userPage.getRecords());
}
// 6. 根据 ID 更新用户(乐观锁生效)
@Test
public void testUpdateById() {
SysUser user = new SysUser();
user.setId(1L);
user.setNickname("张三更新");
user.setPhone("13900139000");
// 乐观锁:如果版本号不匹配,更新失败(避免并发更新冲突)
boolean success = sysUserService.updateById(user);
System.out.println("更新结果:" + success);
}
// 7. 批量更新用户状态
@Test
public void testUpdateBatchStatus() {
<Long> ids<>();
ids.add(1L);
ids.add(2L);
ids.add(3L);
// 将 ID 为 1、2、3 的用户状态改为禁用(status = 0)
boolean success = sysUserService.updateStatusBatch(ids, 0);
System.out.println("批量更新状态结果:" + success);
}
// 8. 逻辑删除用户(并非物理删除,只是将 is_deleted 改为 1)
@Test
public void testRemoveById() {
// 逻辑删除 ID 为 1 的用户
boolean success = sysUserService.removeById(1L);
System.out.println("删除结果:" + success);
// 验证:查询已删除的用户,会自动过滤(is_deleted = 0)
SysUser user = sysUserService.getById(1L);
System.out.println("删除后查询:" + user); // 结果为 null
}
}
三、高级特性与性能优化
在实际开发中,除了基础 CRUD 操作,还需要关注持久层的性能和可扩展性。本节将讲解 MyBatis-Plus 的高级特性,并针对常见的性能瓶颈提供优化方案。
3.1 条件构造器高级用法
MyBatis-Plus 提供了 QueryWrapper(查询条件)、UpdateWrapper(更新条件)、LambdaQueryWrapper(Lambda 表达式条件)三种构造器,其中 LambdaQueryWrapper 可以避免硬编码字段名,减少错误,推荐使用。
java
@Test
public void testLambdaQueryWrapper() {
// LambdaQueryWrapper:使用 Lambda 表达式,避免硬编码字段名
<SysUser> lambdaQueryWrapper =<>();
// 条件:性别为女(gender = 0)、状态正常(status = 1)、创建时间大于 2024-01-01
lambdaQueryWrapper.eq(SysUser::getGender, 0)
.eq(SysUser::getStatus, 1)
.ge(SysUser::getCreateTime, LocalDateTime.of(2024, 1, 1, 0, 0, 0))
.select(SysUser::getId, SysUser::getUsername, SysUser::getNickname); // 只查询指定字段(减少数据传输)<SysUser> userList = sysUserService.list(lambdaQueryWrapper);
System.out.println("Lambda 条件查询结果:" + userList);
}
// UpdateWrapper 用法:根据条件更新,无需设置实体类所有字段
@Test
public void testUpdateWrapper() {
<SysUser> updateWrapper = new UpdateWrapper<>();
// 条件:昵称包含「用户」、状态正常
updateWrapper.like("nickname", "用户")
.eq("status", 1);
// 更新内容:密码改为「654321」、更新时间自动填充
updateWrapper.set("password", "654321");
boolean success = sysUserService.update(updateWrapper);
System.out.println("条件更新结果:" + success);
}
3.2 分页查询优化
分页查询是解决大数据量查询性能问题的核心手段,但如果使用不当,依然会出现性能瓶颈(如 count 查询耗时过长、分页 SQL 效率低)。以下是分页查询的优化方案:
3.2.1 自定义分页 SQL(避免 count 查询)
MyBatis-Plus 的默认分页会自动执行 count 查询(获取总条数),如果数据表数据量极大(百万级以上),count 查询会非常耗时。此时可以自定义分页 SQL,避免 count 查询,或使用更高效的 count 方式。
java
// 1. Mapper 接口中自定义分页方法
@Select("SELECT u.* FROM sys_user u WHERE u.status = #{status} ORDER BY u.create_time DESC LIMIT #{offset}, #{size}")<SysUser> selectUserPage(@Param("status") Integer status, @Param("offset") Long offset, @Param("size") Long size);
// 2. Service 层实现分页逻辑(手动计算分页参数)
@Override
public<SysUser> getUserPageWithoutCount(Integer status, Integer pageNum, Integer pageSize) {
Page<SysUser> page =<>(pageNum, pageSize);
// 计算偏移量:offset = (pageNum - 1) * pageSize
Long offset = (page - 1L) * pageSize;
// 自定义分页查询(不查询总条数)
<SysUser> userList = sysUserMapper.selectUserPage(status, offset, pageSize);
// 设置分页数据(总条数设为 0,避免 count 查询)
page.setRecords(userList);
page.setTotal(0);
return page;
}
3.2.2 分页插件优化(指定数据库方言)
在 MyBatis-Plus 配置类中,分页插件需指定数据库方言(如 MySQL、Oracle),避免分页 SQL 生成错误,同时可以配置分页合理化(避免页码超出范围)。
java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件优化配置
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 开启<= 0 时查询第 1 页,页码 > 总页数时查询最后 1 页
paginationInterceptor.setOverflow(true);
// 设置最大单页限制(避免一次性查询过多数据,默认 500 条)
paginationInterceptor.setMaxLimit(1000L);
interceptor.addInnerInterceptor(paginationInterceptor);
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
3.3 缓存优化(一级缓存 + 二级缓存)
MyBatis 提供了一级缓存(SqlSession 级别)和二级缓存(Mapper 级别),合理使用缓存可以减少数据库查询次数,提升性能。MyBatis-Plus 完全继承了 MyBatis 的缓存机制,以下是具体配置方法。
3.3.1 一级缓存(默认开启)
一级缓存是 SqlSession 级别(Spring 整合 MyBatis 后,SqlSession 由 Spring 管理,每次数据库操作会创建一个新的 SqlSession,因此一级缓存默认无效)。如需启用一级缓存,需配置 SqlSession 手动管理(不推荐,影响并发)。
3.3.2 二级缓存(推荐开启)
二级缓存是 Mapper 级别,多个 SqlSession 可以共享缓存,开启后可以有效减少重复查询。配置步骤如下:
java
// 1. 在 application.yml 中开启二级缓存
mybatis-plus:
configuration:
cache-enabled: true # 开启二级缓存(全局开关)
// 2. 在 Mapper 接口上添加 @CacheNamespace 注解(开启当前 Mapper 的二级缓存)
@Mapper
@CacheNamespace(implementation = org.mybatis.caches.ehcache.EhcacheCache.class) // 使用 EhCache 实现缓存(需引入依赖)
public interface SysUserMapper extends BaseMapper<SysUser> {
// ... 方法省略
}
// 3. 引入 EhCache 依赖(可选,默认使用 MyBatis 内置缓存)<dependency>
<groupId>org.mybatis<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
缓存使用注意事项:
- 缓存的实体类必须实现 Serializable 接口(否则缓存无法序列化存储)。
- 对于频繁更新的数据(如用户余额),不建议使用缓存,避免数据不一致。
- 可以通过 @CacheEvict(清除缓存)、@CachePut(更新缓存)注解管理缓存。
3.4 SQL 优化技巧
持久层的性能瓶颈大多源于低效 SQL,以下是结合 MyBatis-Plus 的 SQL 优化技巧:
- 避免全表扫描:查询时必须添加条件(如 status = 1、is_deleted = 0),避免使用 select *,只查询需要的字段(使用 select 方法指定字段)。
- 合理使用索引:对频繁查询的字段(如 username、phone、role_id)建立索引,提升查询效率。例如:给 username 字段建立唯一索引,给 role_id 字段建立普通索引。
- 批量操作替代循环单条操作:使用 saveBatch、updateBatchById 等批量方法,减少数据库连接次数(批量操作建议每次不超过 1000 条,避免数据库压力过大)。
- 避免嵌套查询:复杂查询尽量使用联表查询(left join、inner join)替代嵌套查询(子查询),提升 SQL 执行效率。
- 使用 @SelectProvider 动态生成 SQL:对于复杂的动态 SQL(如多条件组合查询),使用 @SelectProvider 注解动态生成 SQL,避免 XML 中编写大量冗余代码。
四、常见问题与解决方案
4.1 实体类字段与数据库字段映射失败
原因:实体类字段使用驼峰命名(如 createTime),数据库字段使用下划线命名(如 create_time),未开启驼峰命名映射。
解决方案:在 application.yml 中配置 map-underscore-to-camel-case: true,或使用 @TableField 注解指定字段映射关系。
4.2 分页查询返回空数据(总条数为 0)
原因:未注册分页插件,或分页插件配置错误(未指定数据库方言)。
解决方案:在 MyBatis-Plus 配置类中注册 PaginationInnerInterceptor,并指定数据库类型(DbType.MYSQL)。
4.3 乐观锁更新失败
原因:实体类未添加 @Version 注解,或更新时未携带版本号,导致乐观锁未生效;或并发更新时版本号不匹配。
解决方案:在实体类版本字段上添加 @Version 注解,更新时确保携带版本号(如通过 getById 查询后再更新)。
4.4 逻辑删除后依然能查询到数据
原因:未在实体类字段上添加 @TableLogic 注解,或 MyBatis-Plus 未自动过滤逻辑删除字段。
解决方案:在逻辑删除字段上添加 @TableLogic 注解,确保配置文件中未关闭逻辑删除功能(默认开启)。
五、总结与扩展
本文围绕 Spring Boot 3 + MyBatis-Plus 持久层开发,从基础环境搭建、核心功能实战,到高级特性与性能优化,提供了完整的实战方案和丰富的代码案例,覆盖了实际开发中常见的场景和问题。
MyBatis-Plus 不仅简化了持久层编码,还提供了强大的扩展能力,例如:
- 自定义主键生成策略(如雪花算法、分布式 ID);
- 多数据源整合(配合 dynamic-datasource-spring-boot-starter);
- 代码生成器(AutoGenerator),自动生成 Entity、Mapper、Service、Controller 代码;
- 与 Spring Security、Shiro 等安全框架整合,实现用户认证与授权。