引言:掌握MyBatis后,我还是被CRUD搞崩溃了

自从跟着李哥学会MyBatis,我终于摆脱了“散装SQL”的噩梦——SQL统一写在XML里,代码与SQL解耦,排查问题、修改逻辑都轻松了不少。我一度以为,数据库操作已经达到了“优雅”的顶峰,直到接手公司的新项目,才发现自己还是太天真。

新项目是一个基础管理系统,包含用户、角色、菜单、订单等十几个基础模块,而每个模块都离不开“新增、查询、修改、删除”这一套CRUD操作。按照MyBatis的用法,我需要为每个实体类编写对应的Mapper接口,再在XML文件里编写重复的SQL语句:查询所有用select * from 表名,根据ID查询用select * from 表名 where id = #{id},新增用insert into 表名(字段1,字段2) values(#{字段1},#{字段2})……

仅仅是编写5个模块的CRUD,我就写了近百行重复的SQL和接口方法,手指敲得发麻不说,还频繁出现“字段漏写”“SQL语法错误”的问题。更麻烦的是,当实体类新增一个字段时,我不仅要修改实体类,还要同步修改XML里所有相关的SQL语句,稍有遗漏就会报错。

熬了两个晚上写完基础CRUD后,我顶着黑眼圈向李哥吐槽:“MyBatis虽然解耦了SQL和代码,但重复的CRUD操作也太繁琐了,感觉自己像个只会复制粘贴的工具人。”

李哥笑着递给我一杯咖啡:“这就是你没掌握MyBatis-Plus的原因。MyBatis-Plus是MyBatis的增强工具,它在MyBatis的基础上做了封装,不用你写一行SQL,就能实现大部分CRUD操作,还能轻松搞定分页、条件查询、逻辑删除这些常用功能,让你从重复的CRUD中解放出来,把精力放在核心业务上。”

那天之后,我抱着“摆脱工具人命运”的决心,从零开始学习MyBatis-Plus,从基础配置、一键CRUD,到条件构造、分页插件,再到高级封装、性能优化,一步步解锁了MyBatis-Plus的强大功能。今天,就把这段从“被CRUD折磨”到“一键控库”的成长历程,分享给和曾经的我一样,被重复CRUD困扰的Java新手开发者。

注:本文聚焦MyBatis-Plus 3.5.15(当前最新稳定版本,参考官方最新发布),结合Spring Boot 3.2.x整合实战,全程以“新手小王视角”展开,融入真实开发场景、踩坑经验和解决方案,所有代码块可直接复制使用,兼顾实用性和易懂性,让你从入门到精通,轻松掌握MyBatis-Plus。

第一章:新手破局——MyBatis-Plus是什么?为什么一定要学?

在学习MyBatis-Plus之前,我一直有个疑问:既然已经有了MyBatis,为什么还要学MyBatis-Plus?李哥用一个生动的类比,彻底帮我理清了思路:“如果说MyBatis是‘手动挡汽车’,需要你手动操作离合、油门,全程掌控;那MyBatis-Plus就是‘自动挡汽车’,它帮你简化了所有繁琐的操作,你只需要专注于‘驾驶’(核心业务),不用再纠结于‘换挡’(重复CRUD)。”

简单来说,MyBatis-Plus(简称MP)是baomidou团队开发的MyBatis增强工具,完全兼容MyBatis,不需要你放弃已有的MyBatis知识,就能快速上手。它的核心优势就是“简化开发”,把开发者从重复的CRUD代码中解放出来,同时提供了很多实用的增强功能,让数据库操作更高效、更优雅。据悉,MyBatis-Plus已连续5年获得开源中国年度最佳开源项目殊荣,Github累计16K Star,深受开发者认可。

一、MyBatis-Plus的核心优势(新手必懂)

对比MyBatis,MyBatis-Plus的优势非常明显,尤其是对于新手来说,能节省大量开发时间:

优势 具体说明 新手获益
无SQL一键CRUD 内置BaseMapper接口,继承后即可获得新增、删除、修改、查询等基础CRUD方法,无需编写任何SQL 不用写重复SQL,减少报错
条件构造器 提供QueryWrapper、LambdaQueryWrapper等,支持链式调用,灵活构建复杂查询条件,无需手动拼接SQL 复杂查询更简单,避免SQL注入
自动分页 内置分页插件,配置后即可实现分页查询,无需手动编写limit语句,支持多种数据库 分页功能一键实现,无需手动处理
内置常用功能 支持逻辑删除、自动填充、乐观锁、主键生成策略等,无需手动编写代码实现 减少重复编码,提升开发效率
兼容MyBatis 可以无缝整合MyBatis,已有MyBatis项目可直接迁移,支持XML编写复杂SQL 无需重新学习,快速上手

二、环境准备(Spring Boot整合MyBatis-Plus,新手必看)

学习MyBatis-Plus的第一步,就是搭建整合环境。这里以Spring Boot 3.2.5(当前主流稳定版本) + MyBatis-Plus 3.5.15(官方最新版本) + MySQL 8.0为例,全程手把手教学,新手可直接复制配置,避免踩坑。

1. 导入依赖(pom.xml)

Spring Boot 3.2.x依赖Java 17及以上版本,需先确认本地JDK版本为17+;在pom.xml中,导入MyBatis-Plus的核心依赖、MySQL驱动依赖,无需额外导入MyBatis依赖(MyBatis-Plus已包含):

<!-- Spring Boot 3.2.x 父依赖(已引入可省略) -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
    <relativePath/>
</parent>

<!-- 指定JDK版本为17(Spring Boot 3.2.x 要求) -->
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<!-- MyBatis-Plus 核心依赖(官方最新3.5.15版本) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.15</version>
</dependency>

<!-- MySQL 驱动(MySQL 8.0 对应此版本,适配Spring Boot 3.x) -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.3.0</version>
    <scope>runtime</scope>
</dependency>

<!-- lombok(可选,简化实体类编写,不用写get/set方法) -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<!-- Spring Boot 测试依赖(用于测试CRUD方法) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
2. 配置application.yml(核心配置)

在resources目录下,编写application.yml文件,配置数据库连接信息、MyBatis-Plus相关配置(核心是 mapper-locations,指定mapper接口的位置),适配Spring Boot 3.x语法:

spring:
  # 数据库配置(适配Spring Boot 3.x,驱动类已简化)
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true
    username: root  # 你的MySQL用户名
    password: 123456  # 你的MySQL密码

MyBatis-Plus 配置

mybatis-plus:
  # 指定mapper接口的xml文件位置(如果不写XML,可省略此配置)
  mapper-locations: classpath:mapper/**/*.xml
  # 实体类扫描包(自动识别实体类,简化配置)
  type-aliases-package: com.example.demo.entity
  # 配置日志(可选,打印SQL语句,方便调试,新手推荐开启)
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启驼峰命名自动映射(表中下划线字段对应实体类驼峰字段,默认开启,可显式配置)
    map-underscore-to-camel-case: true
3. 启动类添加注解

在Spring Boot启动类上,添加@MapperScan注解,指定Mapper接口的扫描路径,让Spring能够识别Mapper接口(Spring Boot 3.x语法与2.x一致,无需修改):

package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 扫描Mapper接口所在的包(替换成你自己的Mapper包路径)
@MapperScan("com.example.demo.mapper")
@SpringBootApplication
public class MybatisPlusDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusDemoApplication.class, args);
    }

}
4. 测试环境是否搭建成功

环境搭建完成后,启动项目(需确保JDK17+、MySQL服务正常运行),如果控制台没有报错,且能看到“Connected to the target VM, address: ‘127.0.0.1:xxxx’, transport: ‘socket’”的提示,同时打印MyBatis-Plus相关启动日志,说明环境搭建成功,可以开始学习MyBatis-Plus的核心用法了。

三、新手入门:一键CRUD,告别重复SQL(核心中的核心)

MyBatis-Plus的最核心功能,就是“无SQL一键CRUD”。通过继承BaseMapper接口,我们不需要编写任何SQL语句,就能实现新增、删除、修改、查询等基础操作。下面以“用户管理”为例,李哥手把手教我实现一键CRUD,新手可直接复制代码测试。

1. 编写实体类(Entity)

创建User实体类,对应数据库中的t_user表(数据库表需提前创建,SQL语句附在下方),使用lombok简化get/set方法(如果不使用lombok,需手动编写get/set、toString等方法),适配Spring Boot 3.x的时间类型:

package com.example.demo.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;

// @Data:lombok注解,自动生成get/set、toString、equals等方法
@Data
// @TableName:指定实体类对应的数据表名(如果实体类名和表名一致,可省略)
@TableName("t_user")
public class User {

    // @TableId:指定主键字段,type=IdType.AUTO表示主键自增(MySQL常用)
    @TableId(type = IdType.AUTO)
    private Long id;

    // 用户名(对应表中的username字段,字段名一致可省略@TableField)
    private String username;

    // 密码(对应表中的password字段)
    private String password;

    // 年龄(对应表中的age字段)
    private Integer age;

    // 邮箱(对应表中的email字段)
    private String email;

    // 创建时间(对应表中的create_time字段,下划线命名对应驼峰命名)
    private LocalDateTime createTime;
}
2. 创建数据库表(t_user)

在MySQL中执行以下SQL,创建t_user表,字段与实体类对应,适配MySQL 8.0:

CREATE TABLE `t_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 '年龄',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
3. 编写Mapper接口

创建UserMapper接口,继承MyBatis-Plus提供的BaseMapper接口,无需编写任何方法,即可获得所有基础CRUD功能(与Spring Boot 2.x用法一致,无缝兼容):

package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.springframework.stereotype.Repository;

// @Repository:标识为持久层组件,让Spring识别
@Repository
public interface UserMapper extends BaseMapper<User> {
    // 无需编写任何方法,BaseMapper已提供所有基础CRUD方法
}
4. 编写Service层(可选,推荐)

在实际开发中,我们通常会在Service层封装业务逻辑,李哥告诉我,MyBatis-Plus提供了IService接口和ServiceImpl实现类,进一步简化Service层的开发,Spring Boot 3.x中用法不变。

(1)编写Service接口,继承IService:

package com.example.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.User;

public interface UserService extends IService<User> {
    // 可添加自定义业务方法,基础CRUD方法IService已提供
}

(2)编写Service实现类,继承ServiceImpl,实现UserService:

package com.example.demo.service.impl;

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;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    // 无需编写基础CRUD方法,ServiceImpl已实现,可直接调用
}
5. 测试一键CRUD(核心测试)

我们通过Spring Boot的测试类,测试BaseMapper和IService提供的基础CRUD方法,所有方法无需编写SQL,李哥帮我整理了完整的测试案例,新手可直接复制到测试类中执行:

package com.example.demo;

import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDateTime;
import java.util.List;

// Spring Boot 3.x 测试注解(与2.x的@SpringBootTest一致)
@SpringBootTest
public class UserCrudTest {

    // 注入UserService
    @Autowired
    private UserService userService;

    // 测试新增用户
    @Test
    public void testAddUser() {
        User user = new User();
        user.setUsername("xiaowang");
        user.setPassword("123456");
        user.setAge(22);
        user.setEmail("xiaowang@example.com");
        user.setCreateTime(LocalDateTime.now());
        // 调用IService的save方法,新增用户
        boolean save = userService.save(user);
        System.out.println("新增结果:" + (save ? "成功" : "失败"));
    }

    // 测试根据ID查询用户
    @Test
    public void testSelectById() {
        // 调用IService的getById方法,根据ID查询
        User user = userService.getById(1L);
        System.out.println("查询到的用户:" + user);
    }

    // 测试查询所有用户
    @Test
    public void testSelectAll() {
        // 调用IService的list方法,查询所有
        List<User> userList = userService.list();
        userList.forEach(user -> System.out.println("用户:" + user));
    }

    // 测试修改用户
    @Test
    public void testUpdateUser() {
        User user = new User();
        user.setId(1L); // 必须指定主键ID
        user.setAge(23); // 修改年龄
        user.setEmail("xiaowang_update@example.com"); // 修改邮箱
        // 调用IService的updateById方法,根据ID修改
        boolean update = userService.updateById(user);
        System.out.println("修改结果:" + (update ? "成功" : "失败"));
    }

    // 测试根据ID删除用户
    @Test
    public void testDeleteById() {
        // 调用IService的removeById方法,根据ID删除
        boolean remove = userService.removeById(1L);
        System.out.println("删除结果:" + (remove ? "成功" : "失败"));
    }
}
6. 测试结果说明

执行测试方法后,控制台会打印对应的SQL语句(因为我们开启了日志配置),无需手动编写任何SQL,即可完成所有CRUD操作。李哥提醒我:测试时要注意,先执行新增方法,再执行查询、修改、删除方法,避免因ID不存在导致报错。

第二章:实战进阶——解决MyBatis-Plus新手常踩的“坑”

掌握了一键CRUD后,我以为自己可以顺利上手项目开发,结果刚一开始,就遇到了各种“坑”——主键自增失败、字段映射错误、分页失效……这些问题,也是新手最常遇到的,李哥陪着我逐一排查、解决,每解决一个,我对MyBatis-Plus的理解就深一分。

坑1:主键自增失败,新增用户时ID为null

场景:

执行新增用户测试时,控制台没有报错,但数据库中新增的用户ID为null,或者不是自增的连续数字,导致后续查询、修改、删除方法无法正常使用。

原因分析(李哥拆解):
  1. 实体类中@TableId注解的type属性配置错误,比如设置为IdType.NONE(无主键策略),而不是IdType.AUTO(主键自增);

  2. 数据库表中主键字段没有设置“自增”属性(比如忘记添加AUTO_INCREMENT);

  3. MyBatis-Plus版本与Spring Boot 3.x不兼容(比如使用旧版本的MyBatis-Plus,无法适配JDK17)。

解决方法:
  1. 确认实体类@TableId注解配置正确,MySQL数据库优先使用IdType.AUTO:
// 正确配置(MySQL主键自增)
@TableId(type = IdType.AUTO)
private Long id;
  1. 确认数据库表主键字段设置了自增(参考前面的t_user表SQL,id字段添加了AUTO_INCREMENT);

  2. 确保MyBatis-Plus版本为3.5.15(官方最新版本),与Spring Boot 3.2.x兼容(参考前面的pom.xml依赖配置)。

坑2:字段映射失败,查询结果为null

场景:

执行查询方法时,控制台打印的SQL语句正确,数据库中也有对应的数据,但查询返回的实体类中,部分字段(比如createTime)为null,其他字段正常。

原因分析(李哥拆解):
  1. 实体类字段名与数据库表字段名不一致,且没有开启驼峰命名映射;

  2. 数据库表字段名是下划线命名(比如create_time),实体类字段名是驼峰命名(createTime),但MyBatis-Plus的驼峰映射功能未开启;

  3. 实体类中忘记添加@TableName注解,导致MyBatis-Plus无法识别实体类与数据表的对应关系。

解决方法:
  1. 开启驼峰命名自动映射(在application.yml中配置,前面已配置,可再次确认):
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true # 开启驼峰映射,默认开启,显式配置更稳妥
  1. 如果字段名差异较大(比如数据库是user_name,实体类是userName),可使用@TableField注解指定映射关系:
// 数据库字段是user_name,实体类字段是userName,手动指定映射
@TableField("user_name")
private String userName;
  1. 确认实体类添加了@TableName注解,指定对应的数据表名:
@TableName("t_user") // 必须指定数据表名,避免映射失败
@Data
public class User {
    // 字段信息...
}

坑3:分页失效,查询结果返回所有数据

场景:

配置分页插件后,执行分页查询方法,控制台打印的SQL语句没有limit关键字,返回的是所有数据,分页功能失效。

原因分析(李哥拆解):

MyBatis-Plus的分页功能需要手动配置分页插件,Spring Boot 3.x中,分页插件的配置方式与2.x略有不同,新手容易沿用旧配置,导致分页失效。

解决方法:

在Spring Boot 3.2.x中,编写分页插件配置类,使用MyBatis-Plus 3.5.x的最新配置方式,李哥帮我整理了可直接复制的配置类:

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 {

    // 配置分页插件(Spring Boot 3.x 推荐配置方式)
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件,指定数据库类型(MySQL)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

配置完成后,重启项目,执行分页查询测试,李哥教我编写了分页查询的测试方法:

// 测试分页查询(在UserCrudTest类中添加)
@Test
public void testPage() {
    // 分页参数:当前页(从1开始)、每页显示条数
    Page<User> page = new Page<>(1, 2);
    // 调用IService的page方法,实现分页查询
    Page<User> userPage = userService.page(page);
    // 获取分页结果
    List<User> records = userPage.getRecords(); // 当前页数据
    long total = userPage.getTotal(); // 总条数
    long pages = userPage.getPages(); // 总页数
    System.out.println("总条数:" + total);
    System.out.println("总页数:" + pages);
    System.out.println("当前页数据:");
    records.forEach(user -> System.out.println(user));
}

执行测试后,控制台会打印带有limit关键字的SQL语句,分页功能正常生效。

坑4:逻辑删除配置后,删除方法失效

场景:

配置逻辑删除后,执行删除方法,数据库中的数据没有被真正删除,但也没有报错,查询时却无法查询到该数据。

原因分析(李哥拆解):

逻辑删除的核心是“假删除”——删除时不删除数据,而是修改逻辑删除字段(比如is_deleted)的值,查询时自动过滤逻辑删除的数据。新手容易忘记配置逻辑删除字段,或者配置错误,导致删除方法失效。

解决方法(李哥手把手教):
  1. 给t_user表添加逻辑删除字段is_deleted(0=未删除,1=已删除):
ALTER TABLE `t_user` ADD COLUMN `is_deleted` tinyint NOT NULL DEFAULT 0 COMMENT '逻辑删除(0=未删除,1=已删除)';
  1. 在实体类中添加逻辑删除字段,并添加@TableLogic注解:
package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private Integer age;
    private String email;
    private LocalDateTime createTime;

    // 逻辑删除字段,@TableLogic注解标识
    @TableLogic
    private Integer isDeleted;
}
  1. 在application.yml中配置逻辑删除相关参数(可选,使用默认值可省略):
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: isDeleted # 逻辑删除字段名(与实体类一致)
      logic-delete-value: 1 # 逻辑删除值(已删除)
      logic-not-delete-value: 0 # 逻辑未删除值(未删除)

配置完成后,再次执行删除方法,会发现数据库中的is_deleted字段被修改为1,数据未被真正删除,查询时会自动过滤is_deleted=1的数据,逻辑删除功能正常生效。

第三章:精通之路——MyBatis-Plus高级用法,提升开发效率

解决了新手常踩的“坑”后,我已经能熟练运用MyBatis-Plus完成基础CRUD操作,但李哥告诉我:“这还不够,MyBatis-Plus的高级用法,能帮你节省大量时间,提升开发效率,这才是从‘会用’到‘精通’的关键。”

接下来的一段时间,李哥带着我,逐一掌握了MyBatis-Plus的高级用法,每一个都能直接用到实际工作中,彻底摆脱“工具人”的命运。

一、条件构造器:灵活构建复杂查询(开发高频)

在实际开发中,我们很少会查询所有数据,更多的是根据条件查询(比如根据用户名模糊查询、根据年龄范围查询、多条件组合查询)。MyBatis-Plus提供的条件构造器,能让我们无需编写SQL,通过链式调用,灵活构建复杂的查询条件。

李哥告诉我,新手最常用的两个条件构造器是:QueryWrapper(普通条件构造器)和LambdaQueryWrapper(Lambda条件构造器),其中LambdaQueryWrapper更推荐,能避免字段名写错,提升代码可读性。

1. LambdaQueryWrapper(推荐,新手首选)

以“用户查询”为例,李哥帮我整理了常用的条件查询场景,代码可直接复制使用:

// 在UserCrudTest类中添加测试方法
@Test
public void testLambdaQuery() {
    // 1. 模糊查询:用户名包含“wang”,且年龄大于等于20
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.like(User::getUsername, "wang") // 模糊查询
                     .ge(User::getAge, 20); // 大于等于

    List<User> userList = userService.list(lambdaQueryWrapper);
    System.out.println("模糊查询结果:");
    userList.forEach(System.out::println);

    // 2. 多条件组合:年龄在18-30之间,且邮箱不为null,且未被逻辑删除
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.between(User::getAge, 18, 30) // 年龄在18-30之间
           .isNotNull(User::getEmail) // 邮箱不为null
           .eq(User::getIsDeleted, 0); // 未被逻辑删除

    List<User> list = userService.list(wrapper);
    System.out.println("多条件组合查询结果:");
    list.forEach(System.out::println);

    // 3. 排序查询:按创建时间降序排列,取前3条数据
    LambdaQueryWrapper<User> sortWrapper = new LambdaQueryWrapper<>();
    sortWrapper.orderByDesc(User::getCreateTime) // 按创建时间降序
               .last("limit 3"); // 拼接SQL片段(谨慎使用,避免SQL注入)

    List<User> sortList = userService.list(sortWrapper);
    System.out.println("排序查询结果:");
    sortList.forEach(System.out::println);
}
2. 常用条件构造器方法(新手必记)

李哥帮我整理了新手常用的条件构造器方法,无需死记硬背,用到时查阅即可:

方法 作用
eq 等于(=),比如eq(User::getAge, 20) → age = 20
ne 不等于(≠),比如ne(User::getAge, 20) → age ≠ 20
like 模糊查询(%值%),比如like(User::getUsername, “wang”) → username like ‘%wang%’
likeLeft 左模糊查询(%值),比如likeLeft(User::getUsername, “wang”) → username like ‘%wang’
likeRight 右模糊查询(值%),比如likeRight(User::getUsername, “wang”) → username like ‘wang%’
ge 大于等于(≥),比如ge(User::getAge, 20) → age ≥ 20
gt 大于(>),比如gt(User::getAge, 20) → age > 20
le 小于等于(≤),比如le(User::getAge, 30) → age ≤ 30
lt 小于(<),比如lt(User::getAge, 30) → age < 30
between 在某个范围之间,比如between(User::getAge, 18, 30) → age between 18 and 30
isNotNull 字段不为null,比如isNotNull(User::getEmail) → email is not null
isNull 字段为null,比如isNull(User::getEmail) → email is null
orderByAsc 按字段升序排列,比如orderByAsc(User::getCreateTime) → order by create_time asc
orderByDesc 按字段降序排列,比如orderByDesc(User::getCreateTime) → order by create_time desc

二、自动填充:告别重复的时间字段赋值

在实际开发中,几乎所有实体类都有“创建时间(createTime)”“修改时间(updateTime)”字段,每次新增、修改数据时,都需要手动设置这些字段的值,非常繁琐,还容易遗漏。

李哥告诉我,MyBatis-Plus的自动填充功能,能自动为指定字段赋值,无需手动编写代码,彻底解决这个问题。下面是Spring Boot 3.x中自动填充的完整配置步骤,可直接复制使用。

1. 实体类添加自动填充注解

在需要自动填充的字段上,添加@TableField注解,指定fill属性(FieldFill.INSERT表示新增时填充,FieldFill.INSERT_UPDATE表示新增和修改时都填充):

package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private Integer age;
    private String email;

    // 新增时自动填充(创建时间)
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    // 新增和修改时自动填充(修改时间)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    // 逻辑删除字段
    @TableLogic
    private Integer isDeleted;
}
2. 编写自动填充处理器

创建自动填充处理器类,实现MetaObjectHandler接口,重写insertFill(新增填充)和updateFill(修改填充)方法,李哥帮我整理了完整代码:

package com.example.demo.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

// 自动填充处理器,交给Spring管理
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    // 新增时填充
    @Override
    public void insertFill(MetaObject metaObject) {
        // 填充createTime和updateTime字段,值为当前时间
        strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    // 修改时填充
    @Override
    public void updateFill(MetaObject metaObject) {
        // 填充updateTime字段,值为当前时间
        strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}
3. 测试自动填充功能

配置完成后,执行新增用户方法,无需手动设置createTime和updateTime,数据库中这两个字段会自动填充为当前时间;执行修改用户方法,updateTime会自动更新为当前时间,彻底告别手动赋值的繁琐。

三、代码生成器:一键生成CRUD代码,彻底解放双手

李哥告诉我,MyBatis-Plus的代码生成器(AutoGenerator)是新手的“神器”——它能根据数据库表结构,一键生成实体类、Mapper接口、Service、Controller等所有CRUD相关代码,无需手动编写,极大提升开发效率,尤其适合多模块、多表的大型项目。

下面是Spring Boot 3.2.x整合MyBatis-Plus代码生成器的完整配置,李哥帮我简化了配置,新手可直接复制使用,只需修改数据库连接信息和包路径即可。

1. 导入代码生成器依赖(pom.xml)
<!-- MyBatis-Plus 代码生成器依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.5</version>
</dependency>

<!-- 代码生成器模板依赖(默认使用Velocity模板) -->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>
2. 编写代码生成器类
package com.example.demo.generator;

import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;

public class MyBatisPlusGenerator {

    public static void main(String[] args) {
        // 1. 创建代码生成器对象
        AutoGenerator mpg = new AutoGenerator();

        // 2. 全局配置(核心配置,修改为自己的项目信息)
        GlobalConfig gc = new GlobalConfig();
        // 获取当前项目根路径(无需手动修改,自动获取)
        String projectPath = System.getProperty("user.dir");
        // 生成文件的输出目录(拼接路径,可根据自己的项目结构调整)
        gc.setOutputDir(projectPath + "/src/main/java");
        // 作者信息(填写自己的姓名/昵称)
        gc.setAuthor("xiaowang");
        // 生成后是否打开输出目录(true=打开,false=不打开,新手推荐true)
        gc.setOpen(true);
        // 生成实体类时,是否生成 serialVersionUID(序列化ID,推荐开启)
        gc.setSwagger2(false); // 新手可先关闭,后续需要再开启
        // 避免生成重复文件
        gc.setFileOverride(true);
        // 设置实体类、Mapper等文件的命名规则(无需修改,默认即可)
        gc.setEntityName("%s"); // 实体类命名:User.java(不添加前缀)
        gc.setMapperName("%sMapper"); // Mapper接口命名:UserMapper.java
        gc.setServiceName("%sService"); // Service接口命名:UserService.java
        gc.setServiceImplName("%sServiceImpl"); // Service实现类命名:UserServiceImpl.java
        gc.setControllerName("%sController"); // Controller命名:UserController.java
        // 将全局配置设置到生成器中
        mpg.setGlobalConfig(gc);

        // 3. 数据源配置(关键!修改为自己的MySQL连接信息)
        DataSourceConfig dsc = new DataSourceConfig();
        // 数据库类型(MySQL,固定写法)
        dsc.setDbType(com.baomidou.mybatisplus.generator.config.rules.DbType.MYSQL);
        // 数据库驱动类(MySQL 8.0 固定写法,无需修改)
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        // 数据库连接URL(修改为自己的数据库地址、数据库名)
        dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true");
        // 数据库用户名(修改为自己的MySQL用户名,默认root)
        dsc.setUsername("root");
        // 数据库密码(修改为自己的MySQL密码)
        dsc.setPassword("123456");
        // 将数据源配置设置到生成器中
        mpg.setDataSource(dsc);

        // 4. 包配置(修改为自己的项目包路径)
        PackageConfig pc = new PackageConfig();
        // 父包名(核心!替换成自己的项目父包,比如com.example.demo)
        pc.setParent("com.example.demo");
        // 实体类包名(默认entity,无需修改)
        pc.setEntity("entity");
        // Mapper接口包名(默认mapper,无需修改)
        pc.setMapper("mapper");
        // Service接口包名(默认service,无需修改)
        pc.setService("service");
        // Service实现类包名(默认service.impl,无需修改)
        pc.setServiceImpl("service.impl");
        // Controller包名(默认controller,无需修改)
        pc.setController("controller");
        // 将包配置设置到生成器中
        mpg.setPackageInfo(pc);

        // 5. 策略配置(生成规则,新手无需修改,直接使用)
        StrategyConfig strategy = new StrategyConfig();
        // 指定要生成的数据库表名(关键!填写自己的表名,多个表用逗号分隔)
        // 例:strategy.setInclude("t_user", "t_role", "t_menu")
        strategy.setInclude("t_user");
        // 排除不需要生成的表(没有则不写)
        strategy.setExclude();
        // 数据库表名映射到实体类的命名策略(下划线转驼峰,默认,无需修改)
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 数据库表字段名映射到实体类属性的命名策略(下划线转驼峰,默认,无需修改)
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 实体类是否添加@Data注解(lombok,推荐开启,需导入lombok依赖)
        strategy.setEntityLombokModel(true);
        // 实体类是否添加逻辑删除字段(如果表中有is_deleted字段,开启此配置)
        strategy.setLogicDeleteFieldName("isDeleted");
        // Controller是否开启Rest风格(@RestController,推荐开启)
        strategy.setRestControllerStyle(true);
        // 去除Controller类名的"Controller"后缀(false=保留,true=去除,新手推荐false)
        strategy.setControllerMappingHyphenStyle(false);
        // 将策略配置设置到生成器中
        mpg.setStrategy(strategy);

        // 6. 模板引擎配置(默认使用Velocity,无需修改)
        mpg.setTemplateEngine(new VelocityTemplateEngine());

        // 7. 执行代码生成(一键生成,点击运行此类的main方法即可)
        mpg.execute();
    }
}
3. 代码生成器使用步骤(新手必看)

配置完成后,使用方法非常简单,李哥手把手教我操作,3步即可生成所有CRUD代码:

  1. 确认pom.xml中已导入代码生成器依赖和lombok依赖(前面已配置,无需重复操作);

  2. 修改MyBatisPlusGenerator类中的3处核心配置:① 全局配置中的作者信息;② 数据源配置中的MySQL连接信息(URL、用户名、密码);③ 策略配置中的数据库表名(setInclude方法中填写自己的表名);

  3. 运行MyBatisPlusGenerator类的main方法,等待1-2秒,代码会自动生成到指定的包路径下,生成的文件包括:实体类、Mapper接口、Mapper XML文件(可选)、Service接口、Service实现类、Controller类。

4. 生成代码说明(新手必懂)

生成完成后,无需手动修改任何代码,即可直接使用,李哥帮我梳理了生成的核心文件及作用:

  • entity:实体类,已自动添加@Data、@TableName、@TableId等注解,逻辑删除字段、自动填充字段也会自动生成(需提前在表中添加对应字段);

  • mapper:Mapper接口,已自动继承BaseMapper,包含所有基础CRUD方法,无需手动编写;

  • service:Service接口和实现类,已自动继承IService和ServiceImpl,基础CRUD方法已实现,可直接调用;

  • controller:Controller类,已自动生成REST风格的接口(get/post请求),包含基础的CRUD接口,可根据业务需求修改。

5. 注意事项

李哥特别提醒我,使用代码生成器时,有2个细节新手容易忽略,避免踩坑:

  1. 生成代码前,需确保数据库表已创建,且表字段与实体类字段对应(代码生成器会根据表结构自动生成实体类,字段名自动下划线转驼峰);

  2. 如果多次运行生成器,会覆盖已有的代码,建议生成后不要轻易重新运行,若需修改表结构,可删除原有生成文件后再重新生成。

四、乐观锁:解决并发修改冲突(实战必备)

在实际项目中,并发修改是非常常见的场景——比如两个用户同时修改同一个用户的信息,第一个用户修改后,第二个用户的修改会覆盖第一个用户的结果,导致数据不一致。李哥告诉我,MyBatis-Plus的乐观锁功能,能轻松解决这个问题,无需手动编写并发控制代码。

乐观锁的核心原理:在实体类中添加一个版本字段(比如version),每次修改数据时,版本号自动加1,修改时会判断当前版本号是否与数据库中的版本号一致,一致则修改,不一致则修改失败(说明数据已被其他用户修改)。

1. 配置乐观锁(3步完成)

(1)给t_user表添加版本字段version(默认值为1):

ALTER TABLE `t_user` ADD COLUMN `version` int NOT NULL DEFAULT 1 COMMENT '乐观锁版本号';

(2)在实体类中添加version字段,并添加@Version注解:

package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private Integer age;
    private String email;

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableLogic
    private Integer isDeleted;

    // 乐观锁版本字段,@Version注解标识
    @Version
    private Integer version;
}

(3)在分页插件配置类中,添加乐观锁插件(与分页插件配置在一起,无需单独创建类):

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.OptimisticLockerInnerInterceptor;
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));
        // 乐观锁插件(添加这一行即可)
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}
2. 测试乐观锁功能

配置完成后,李哥帮我编写了并发修改的测试方法,模拟两个用户同时修改同一个用户的信息,测试乐观锁是否生效:

// 在UserCrudTest类中添加测试方法
@Test
public void testOptimisticLocker() {
    // 1. 第一个用户查询用户信息(此时version=1)
    User user1 = userService.getById(1L);
    // 2. 第二个用户同时查询同一个用户信息(此时version也=1)
    User user2 = userService.getById(1L);

    // 3. 第一个用户修改年龄为25,修改成功后version自动变为2
    user1.setAge(25);
    userService.updateById(user1);

    // 4. 第二个用户修改年龄为26,此时数据库中version已变为2,与user2的version=1不一致,修改失败
    user2.setAge(26);
    boolean update = userService.updateById(user2);
    System.out.println("第二个用户修改结果:" + (update ? "成功" : "失败"));

    // 5. 查询最终结果,年龄为25(第一个用户的修改生效,第二个用户的修改失败)
    User finalUser = userService.getById(1L);
    System.out.println("最终用户年龄:" + finalUser.getAge());
}
3. 测试结果说明

执行测试后,控制台会打印“第二个用户修改结果:失败”,最终用户年龄为25,说明乐观锁已生效。这样就避免了并发修改导致的数据不一致问题,无需手动编写复杂的并发控制代码,极大简化了开发。

第四章:实战总结——从新手到精通的成长路径

跟着李哥学习MyBatis-Plus的这段时间,我从一个“被CRUD折磨的工具人”,变成了能轻松掌控数据库操作的开发者,不仅节省了大量开发时间,还提升了代码的优雅度和可维护性。结合自己的学习经历,李哥帮我总结了MyBatis-Plus的学习路径,新手可以参考这个路径,快速上手、精通:

第一步:夯实基础(1-2天)

  1. 理解MyBatis-Plus的核心定位:MyBatis的增强工具,完全兼容MyBatis,核心是“简化开发”;

  2. 完成Spring Boot 3.x与MyBatis-Plus的环境整合,掌握依赖导入、配置文件编写、启动类注解配置;

  3. 掌握一键CRUD的使用:继承BaseMapper和IService,实现基础的新增、删除、修改、查询操作,能独立完成测试。

第二步:避坑进阶(2-3天)

  1. 重点解决新手常踩的4个坑:主键自增失败、字段映射失败、分页失效、逻辑删除配置错误;

  2. 熟练掌握条件构造器的使用,尤其是LambdaQueryWrapper,能灵活构建复杂的查询条件;

  3. 掌握自动填充功能,实现时间字段的自动赋值,告别手动设置。

第三步:精通高级用法(3-4天)

  1. 熟练使用代码生成器,能根据数据库表结构,一键生成所有CRUD相关代码,提升开发效率;

  2. 掌握乐观锁的配置和使用,解决并发修改冲突问题;

  3. 了解MyBatis-Plus的其他高级功能,比如拦截器、自定义SQL、多表查询等(根据业务需求深入学习)。

最后:实战落地(持续练习)

李哥说,学习MyBatis-Plus的核心是“多练、多踩坑、多总结”。新手可以搭建一个简单的管理系统(比如用户管理、订单管理),将所学的知识点逐一应用到实战中,遇到问题多排查、多查阅官方文档(MyBatis-Plus官方文档),久而久之,就能熟练运用MyBatis-Plus,彻底摆脱重复CRUD的困扰。

写在最后:MyBatis-Plus的核心价值,是让开发者从重复、繁琐的CRUD操作中解放出来,把更多的精力放在核心业务逻辑上。它不是“花里胡哨”的工具,而是能真正提升开发效率、降低开发成本的“实战神器”。希望这篇实战指南,能帮助和曾经的我一样的Java新手,快速掌握MyBatis-Plus,少走弯路,早日摆脱“工具人”的命运,成为更优秀的开发者!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐