MyBatis-Plus

技术栈
后端框架
javaormmybatisspring-bootcrud

概览

MyBatis-Plus 技术栈概览

MyBatis-Plus 是苞米豆团队在 MyBatis 基础上开发的增强工具,专为简化单表 CRUD 而生。它不改变 MyBatis 原生能力,只是在其上做增强——通用 Mapper、分页插件、逻辑删除、乐观锁、代码生成器等,大幅减少样板代码。

解决什么问题

  • MyBatis 单表 CRUD 每次都要写 XML → BaseMapper 自动生成
  • 复杂分页手动 count → Page<T> 自动分页
  • 逻辑删除手动写关联更新 → @TableLogic 注解一键
  • 数据库字段映射重复劳动 → 自动驼峰/下划线映射
  • 多租户/乐观锁/防全表更新 → 插件机制即插即用

关键特性

  • BaseMapper:通用 CRUD 接口,继承即用
  • IService + ServiceImpl:通用 Service 层封装
  • 条件构造器 Wrapper:Lambda 链式查询,类型安全
  • 分页插件PaginationInnerInterceptor 物理分页
  • 逻辑删除@TableLogic 标记,delete → update
  • 乐观锁@Version 注解,更新时自动校验
  • 自动填充:创建时间/更新时间自动填入
  • 代码生成器:根据数据库表自动生成 Entity/Mapper/Service/Controller

安装

环境准备

  • JDK:8(MyBatis-Plus 3.5+ 推荐 JDK 8-21)
  • Spring Boot:2.x / 3.x
  • 数据库:MySQL 5.7+ / PostgreSQL / H2 等
  • IDE:IntelliJ IDEA(推荐)+ MyBatisX 插件

安装命令

Maven

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.5</version>
</dependency>

Gradle

implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.5'

完整 Spring Boot 依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.5</version>
</dependency>

配置

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true  # 驼峰自动映射
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # SQL 日志
  global-config:
    db-config:
      id-type: auto        # 主键自增
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

常见问题

Q1: Invalid bound statement (not found)

Mapper XML 或接口不被扫描。检查 @MapperScan 注解路径与 Mapper 包路径是否一致。

Q2: 分页不生效

需注册分页插件:

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor i = new MybatisPlusInterceptor();
        i.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return i;
    }
}

Q3: 逻辑删除后查询仍出现

确认 @TableLogic 注解正确,且 entity 中有对应字段。

示例

MyBatis-Plus 例程:用户增删改查

目标

展示 MyBatis-Plus 核心:Entity 定义、BaseMapper CRUD、LambdaQueryWrapper 条件查询、分页、逻辑删除。

完整代码

// ── User.java (Entity) ──
package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import java.time.LocalDateTime;
import lombok.Data;

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;
    private Integer age;
    private String email;

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

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

    @TableLogic
    private Integer deleted;
}
// ── 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 方法!
    // 自定义 SQL 可在这里声明,XML 实现
}
// ── UserService.java ──
package com.example.demo.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    // 插入
    public User create(String name, Integer age, String email) {
        User user = new User();
        user.setName(name);
        user.setAge(age);
        user.setEmail(email);
        userMapper.insert(user);
        return user;
    }

    // 条件查询:Lambda 链式写法(类型安全,防字段拼写错误)
    public List<User> findByAgeRange(Integer minAge, Integer maxAge) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.between(User::getAge, minAge, maxAge)
               .orderByDesc(User::getId);
        return userMapper.selectList(wrapper);
    }

    // 分页查询
    public Page<User> page(int pageNum, int pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        return userMapper.selectPage(page, null);
    }

    // 更新
    public boolean updateEmail(Long id, String email) {
        User user = new User();
        user.setId(id);
        user.setEmail(email);
        return userMapper.updateById(user) > 0;
    }

    // 逻辑删除(自动转为 UPDATE SET deleted=1)
    public boolean delete(Long id) {
        return userMapper.deleteById(id) > 0;
    }
}
// ── 控制器 ──
package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

    @Resource
    private UserService userService;

    @PostMapping
    public User create(@RequestParam String name,
                       @RequestParam Integer age,
                       @RequestParam String email) {
        return userService.create(name, age, email);
    }

    @GetMapping
    public List<User> list(@RequestParam(defaultValue = "0") Integer minAge,
                           @RequestParam(defaultValue = "100") Integer maxAge) {
        return userService.findByAgeRange(minAge, maxAge);
    }

    @DeleteMapping("/{id}")
    public String delete(@PathVariable Long id) {
        return userService.delete(id) ? "删除成功" : "删除失败";
    }
}

运行步骤

# 1. 数据库建表
CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    email VARCHAR(100),
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted INT DEFAULT 0
);

# 2. 启动 Spring Boot
mvn spring-boot:run

# 3. 测试
curl -X POST "http://localhost:8080/users?name=张三&age=25&email=zhangsan@example.com"
curl "http://localhost:8080/users"

常用条件构造器速查

// 等值
wrapper.eq(User::getName, "张三")
// 模糊
wrapper.like(User::getEmail, "@gmail.com")
// 范围
wrapper.between(User::getAge, 20, 30)
// IN
wrapper.in(User::getId, 1L, 2L, 3L)
// 分组
wrapper.groupBy(User::getAge)
// 复杂 AND/OR
wrapper.and(w -> w.eq(User::getAge, 18).or().eq(User::getAge, 20))

教程

MyBatis-Plus 实战入门教程

第一章:MyBatis vs MyBatis-Plus

MyBatis 的痛点:

// MyBatis 原始写法——每个单表查询都要写 SQL
@Select("SELECT * FROM user WHERE age BETWEEN #{min} AND #{max}")
List<User> findByAgeRange(@Param("min") int min, @Param("max") int max);

MyBatis-Plus 让这些消失:

// 一行 SQL 不用写
List<User> users = userMapper.selectList(
    new LambdaQueryWrapper<User>().between(User::getAge, minAge, maxAge)
);

第二章:Entity 注解详解

注解 说明
@TableName("user") 指定表名(默认类名驼峰转下划线)
@TableId(type=IdType.AUTO) 主键策略:AUTO/ASSIGN_ID(雪花)/INPUT
@TableField("nick_name") 指定字段映射(默认驼峰→下划线)
@TableField(exist=false) 标记非数据库字段
@TableField(fill=Fill.INSERT) 插入时自动填充(配合 MetaObjectHandler)
@TableLogic 逻辑删除字段
@Version 乐观锁版本号

第三章:条件构造器进阶

Wrapper 层级

Wrapper
├── AbstractWrapper(条件)
│   ├── QueryWrapper(普通查询)
│   │   └── LambdaQueryWrapper(Lambda 类型安全查询)
│   └── UpdateWrapper(更新条件)
│       └── LambdaUpdateWrapper
└── AbstractChainWrapper(链式调用)

常用操作

// 查询数量
long count = userMapper.selectCount(
    new LambdaQueryWrapper<User>().gt(User::getAge, 18)
);

// 查询单个
User user = userMapper.selectOne(
    new LambdaQueryWrapper<User>().eq(User::getEmail, "test@test.com")
);

// 只查指定字段
wrapper.select(User::getId, User::getName);

// 排除字段
wrapper.select(User.class, info ->
    !info.getColumn().equals("password")
);

第四章:分页插件

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor interceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页
        interceptor.addInnerInterceptor(
            new PaginationInnerInterceptor(DbType.MYSQL)
        );
        // 防全表更新/删除
        interceptor.addInnerInterceptor(
            new BlockAttackInnerInterceptor()
        );
        return interceptor;
    }
}

第五章:代码生成器

MP 的代码生成器可以一键生成 Entity、Mapper、Service、Controller:

FastAutoGenerator.create("jdbc:mysql://localhost:3306/test", "root", "root")
    .globalConfig(builder -> builder.author("dev").outputDir("src/main/java"))
    .packageConfig(builder -> builder.parent("com.example"))
    .strategyConfig(builder -> builder.addInclude("user").entityBuilder().enableLombok())
    .execute();

思考题

  1. MyBatis-Plus 的 BaseMapper 底层是怎么实现的?它怎么知道 selectById 要去哪张表?
  2. 逻辑删除和物理删除各适合什么场景?生产环境为什么更推荐逻辑删除?
  3. LambdaQueryWrapper 和普通 QueryWrapper 的区别是什么?前者怎么做到类型安全的?

参考资料

暂无参考文献