Skip to content

MyBatis-Plus ​

MyBatis-Plus

MyBatis-Plus 是 MyBatis 的增强工具,不改变 MyBatis,只做增强,核心目标是不写 SQL 也能完成单表 CRUD。

依赖 ​

Spring Boot 4 使用 spring-boot4-starter,从 3.5.13 开始支持,最新版本 3.5.17

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot4-starter</artifactId>
    <version>3.5.17</version>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

注意:版本要和 Spring Boot 版本匹配,Spring Boot 3 用 mybatis-plus-spring-boot3-starter,Spring Boot 2 用 mybatis-plus-boot-starter

配置 ​

yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456

mybatis-plus:
  # 1. 基础配置
  mapper-locations: classpath*:/mapper/**/*.xml
  type-aliases-package: com.example.demo.entity
  config-location: classpath:mybatis-config.xml   # 指定外部 MyBatis 配置文件(一般不用)

  # 2. 原生 MyBatis 配置(继承自 MyBatis)
  configuration:
    map-underscore-to-camel-case: true            # 驼峰命名自动映射(默认 true)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl   # 控制台打印 SQL(开发环境用)
    cache-enabled: false                          # 关闭二级缓存
    default-enum-type-handler: ...                # 枚举类型处理器(可选)

  # 3. 全局策略配置
  global-config:
    banner: false                                 # 关闭控制台 Banner
    db-config:
      id-type: assign_id                          # 主键策略(默认雪花算法)
      logic-delete-field: deleted                 # 逻辑删除字段名(全局配置)
      logic-delete-value: 1                       # 逻辑已删除值
      logic-not-delete-value: 0                   # 逻辑未删除值
      # table-prefix: t_                          # 表名前缀(可选)

在Spring Boot启动类中添加@MapperScan注解,扫描Mapper文件夹

java
package org.example._demo;

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

@SpringBootApplication
@MapperScan("org.example._demo.mapper")
public class Application {

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

Mapper 接口和 Service 层的具体写法放在文末,见 Mapper、Service

常见注解 ​

java
@Data
@TableName("user")                        // 类名和表名不一致时指定
public class User {

    @TableId(type = IdType.AUTO)          // 主键
    private Long id;

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

    @TableField("phone_num")              // 字段名和属性名不一致时指定
    private String phoneNum;

    @TableField(fill = FieldFill.INSERT)  // 插入时自动填充
    private LocalDateTime createTime;
    
    @TableField("`order`") 				  // 成员变量名与数据库关键字冲突
    private Integer order;
    
    @TableField(exist = false)            // 在数据库中不存在
    private String address;

    @TableLogic                           // 逻辑删除标记
    private Integer deleted;
}

常用注解

注解作用
@TableName指定表名
@TableId指定主键,type 指定生成策略
@TableField指定列名、排除字段、自动填充
@TableLogic逻辑删除字段
@Version乐观锁版本字段

IdType 常见取值

  • AUTO:数据库自增
  • ASSIGN_ID:雪花算法(默认),生成全局唯一 ID
  • INPUT:手动赋值

用法 ​

Mapper继承BaseMapper<User>后就有整套单表的CRUD,Service层继承IService<User>可以拿到另一套封装方法

java
// mapper层
public interface UserMapper extends BaseMapper<User> {
}
// service层
public interface UserService extends IService<User> {
}
// impl层
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {
}

Mapper方法返回受影响行数int:

java
// C
User user = new User();
user.setName("张三");
int rows = userMapper.insert(user);

// R
User u1 = userMapper.selectById(1L);
List<User> all = userMapper.selectList(null);

// U
user.setId(1L);
user.setName("李四");
userMapper.updateById(user);

// D
userMapper.deleteById(1L);
userMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L));

Service方法统一返回boolean或集合,命名更容易和Mapper区分:

java
// C
userService.save(user);

// R
User u = userService.getById(1L);
List<User> list = userService.list();

// U
userService.updateById(user);

// D
userService.removeById(1L);
userService.removeByIds(Arrays.asList(1L, 2L));

Wrapper条件写法 ​

复杂查询和更新推荐用Lambda写法,编译器能检验字段名:

java
// 条件查询
LambdaQueryWrapper<User> qw = Wrappers.<User>lambdaQuery()
        .like(User::getName, "张")
        .ge(User::getAge, 18)
        .orderByDesc(User::getId);

List<User> users = userMapper.selectList(qw);
User one = userMapper.selectOne(qw);

// 按条件更新:实体存新值,wrapper 存 where
User update = new User();
update.setName("王五");
userMapper.update(update,
        Wrappers.<User>lambdaUpdate().eq(User::getId, 1L));

// 直接 set 字段并带条件
userService.update(Wrappers.<User>lambdaUpdate()
        .set(User::getEmail, "new@example.com")
        .eq(User::getId, 1L));

// 条件删除
userService.remove(Wrappers.<User>lambdaQuery().lt(User::getAge, 18));

批量操作 ​

java
userService.saveBatch(list);            // 批量插入
userService.updateBatchById(list);      // 批量更新
userService.removeByIds(idList);        // 批量删除

updateById 注意点 ​

updateById 只更新非 null 字段,如果想把字段清空(设为 null),需要用wrapper的set

java
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 1).set("email", null);
userService.update(null, wrapper);

条件构造器 ​

Wrapper家族

从类型上分,核心是abstractWrapper,它提供了绝大多数条件方法;下面派生两类:

Wrapper
└─ AbstractWrapper
   ├─ QueryWrapper             // 查询条件 + select 字段
   ├─ UpdateWrapper            // 更新条件 + set 字段
   ├─ LambdaQueryWrapper       // QueryWrapper 的 Lambda 版本
   └─ LambdaUpdateWrapper      // UpdateWrapper 的 Lambda 版本

以下是QueryWrapper和LambdaQueryWrapper的演示

java
    @Test
    void testQueryWrapper() {
        // 构建查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<User>()
                .select("id", "username", "info", "balance")
                .like("username", "ww")
                .ge("balance", 500);

        // 查询
        List<User> userList = userMapper.selectList(queryWrapper);
        userList.forEach(System.out::println);
    }

    @Test
    void testUpdateByQueryWrapper() {
        // 要更新的数据
        User user = new User();
        user.setBalance(BigDecimal.valueOf(2000));
        // 更新的条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .eq("username", "ww");
        // 执行更新
        userMapper.update(user, wrapper);
    }

    @Test
    void testUpdateWrapper() {
        List<Integer> ids = List.of(1, 2, 3);
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", ids);
        userMapper.update(null, wrapper);
    }

    @Test
    void testLambdaQueryWrapper() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
                .like(User::getUsername, "ww")
                .ge(User::getBalance, 500);
        List<User> userList = userMapper.selectList(wrapper);
        userList.forEach(System.out::println);
    }

区别只有一个核心点:QueryWrapper里写的是数据库字段字符串,容易写错且不支持编译器检查LambdaQueryWrapper用user::getName这类方法引用,字段名改动时代码会直接报错,日常开发推荐后者

java
List<User> users = userService.list(
    Wrappers.<User>lambdaQuery()
        .eq(User::getStatus, 1)                    // status = 1
        .ne(User::getDeleted, 1)                   // deleted <> 1
        .gt(User::getAge, 18)                      // age > 18
        .ge(User::getScore, 60)                    // score >= 60
        .between(User::getCreateTime, start, end)  // BETWEEN ... AND ...
        .like(User::getName, "张")                  // name LIKE '%张%'
        .likeRight(User::getPhone, "138")          // phone LIKE '138%'
        .in(User::getRoleId, Arrays.asList(1L, 2L))// role_id IN (...)
        .isNotNull(User::getEmail)                 // email IS NOT NULL
        .orderByDesc(User::getCreateTime)          // ORDER BY create_time DESC
);

动态参数处理 ​

最常见的一个实用点是:条件方法第一个参数可以传boolean,为false时该条件不生效

java
List<User> users = userService.list(
    Wrappers.<User>lambdaQuery()
        .like(StringUtils.hasText(name), User::getName, name)
        .eq(status != null, User::getStatus, status)
        .between(ageMin != null && ageMax != null,
                 User::getAge, ageMin, ageMax)
        .orderByDesc(User::getCreateTime)
);
更新 ​

LambdaUpdateWrapper既能放where,也能放set

java
boolean updated = userService.update(
    Wrappers.<User>lambdaUpdate()
        .set(User::getStatus, 0)
        .set(User::getUpdateTime, LocalDateTime.now())
        .eq(User::getId, 100L)
);

// 需要 SQL 运算时用 setSql,例如浏览量自增
userService.update(
    Wrappers.<User>lambdaUpdate()
        .setSql("view_count = view_count + 1")
        .eq(User::getId, 100L)
);
删除 ​
java
boolean removed = userService.remove(
    Wrappers.<User>lambdaQuery().lt(User::getAge, 18)
);

常用方法

方法对应 SQL
eq / ne= / !=
gt / ge / lt / le> / >= / < / <=
like / likeLeft / likeRightLIKE '%x%' / LIKE '%x' / LIKE 'x%'
betweenBETWEEN a AND b
inIN (...)
isNull / isNotNullIS NULL / IS NOT NULL
and / or连接条件
orderByAsc / orderByDesc排序
groupBy / having分组 / 过滤
select指定查询列
last追加 SQL 片段,如 .last("limit 1")

条件方法第一个参数都是 boolean,为 false 时跳过该条件,适合动态拼接

java
wrapper.like(StringUtils.hasText(name), User::getName, name);

IService ​

IService<T>配合ServiceImpl<M,N>使用。把BaseMapper的单表查询CRUD进一步封装到Service层

Service接口

java
public interface UserService extends IService<User> {
}

Service实现

java
@Service
public class UserServiceImpl 
        extends ServiceImpl<UserMapper, User> 
        implements UserService {
}

泛型含义:

  • IService<User>:表示这个Service处理User实体
  • ServiceImpl<UserMapper, User>:第一个是Mapper类型,第二个是实体类型
  • ServiceImpl内部会注入UserMapper,并提供getBaseMapper()获取它

新增/保存 ​

java
userService.save(user);
userService.saveBatch(list);
userService.saveBatch(list, 500);
userService.saveOrUpdate(user);
userService.saveOrUpdateBatch(list);

saveOrUpdate 判断逻辑通常是:主键为空或根据主键查不到,则 insert;否则 update。注意它主要看主键,不是唯一索引。

删除 ​

java
userService.removeById(1L);
userService.removeByIds(ids);
userService.removeByMap(map);
userService.remove(wrapper);

修改 ​

java
userService.updateById(user);
userService.update(user, wrapper);
userService.updateBatchById(list);

查询 ​

java
User user = userService.getById(1L);
User one = userService.getOne(wrapper, false);
List<User> list = userService.list();
List<User> list2 = userService.list(wrapper);
List<User> list3 = userService.listByIds(ids);
long count = userService.count(wrapper);

getOne(wrapper)如果查询到多条会抛异常,确定只有一条时可用getOne(wrapper, false)取第一条

LambdaQuery ​

在ServiceImpl使用,直接链式调用,无需new对象,最后以.list()、.one()等方法返回结果

java
// 在 UserServiceImpl 中
public List<User> getUsers(String name, Integer minAge) {
    return lambdaQuery()
            .like(StringUtils.isNotBlank(name), User::getUsername, name)
            .ge(minAge != null, User::getAge, minAge)
            .list(); // 直接返回 List<User>
}

而LambdaQueryWrapper更灵活,需要手动创建Wrapper对象,然后显示交给BaseMapper执行

java
// 在 UserServiceImpl 中,或者任何能拿到 baseMapper 的地方
public List<User> getUsers(String name, Integer minAge) {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.like(StringUtils.isNotBlank(name), User::getUsername, name)
           .ge(minAge != null, User::getAge, minAge);
    return baseMapper.selectList(wrapper); // 需手动调用 baseMapper
}

优先使用LambdaQuery

分页插件 ​

v3.5.9 之后分页插件需要单独引入 mybatis-plus-jsqlparser

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser</artifactId>
    <version>3.5.17</version>
</dependency>

分页需要配置分页插件,否则不会真正分页:

java
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

使用

java
Page<User> page = new Page<>(1, 10);                    // 第 1 页,每页 10 条
Page<User> result = userMapper.selectPage(page, wrapper);
result.getRecords();      // 当前页数据
result.getTotal();        // 总条数
result.getPages();        // 总页数

逻辑删除 ​

实体字段加 @TableLogic 后,删除变成更新 deleted 字段,查询自动过滤

sql
-- 执行 removeById(1) 实际生成
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0

需要在配置里指定逻辑删除的值

yml
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

自动填充 ​

常用于自动填充 createTime、updateTime

实体字段加 fill

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

@TableField(fill = FieldFill.INSERT_UPDATE) // 只在修改时填充
private LocalDateTime updateTime;

还要实现 MetaObjectHandler,在config目录

java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
    }
}

乐观锁 ​

适合读多写少的场景,先正常读,不着急加锁,真正修改时再检查有没有冲突

具体实现: ​

实体字段加 @Version,配合 OptimisticLockerInnerInterceptor 插件

java
@Version
private Integer version;

配置插件

java
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {

        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 乐观锁插件
        interceptor.addInnerInterceptor(
                new OptimisticLockerInnerInterceptor()
        );

        return interceptor;
    }
}

更新时自动带上版本条件

sql
UPDATE user SET name = 'x', version = version + 1 WHERE id = 1 AND version = 0

代码生成器 ​

一键生成 entity / mapper / service / controller

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.17</version>
</dependency>
java
FastAutoGenerator.create("jdbc:mysql://localhost:3306/db", "root", "密码")
    .globalConfig(builder -> builder
        .author("huangshuheng")
        .outputDir(System.getProperty("user.dir") + "/src/main/java"))
    .packageConfig(builder -> builder
        .parent("com.example")
        .entity("entity")
        .mapper("mapper")
        .service("service")
        .controller("controller"))
    .strategyConfig(builder -> builder
        .addInclude("user"))          // 指定表
    .execute();

增删改查常用操作 ​

增(Insert) ​

操作方法说明
插入单条insert(entity) / save(entity)id 自动回填
批量插入saveBatch(list)默认分批 1000 条执行
存在则更新,否则插入saveOrUpdate(entity)按主键判断

删(Delete) ​

操作方法说明
按 id 删deleteById(id) / removeById(id)
按 id 集合删deleteByIds(ids) / removeByIds(ids)对应 IN (...)
按条件删delete(wrapper) / remove(wrapper)用条件构造器指定
逻辑删除加 @TableLogic 后所有删除自动变 UPDATE不真删数据

改(Update) ​

操作方法说明
按 id 更新updateById(entity)只更新非 null 字段
按条件更新update(entity, wrapper)wrapper 指定更新范围
按条件直接 setupdate(null, wrapper)用 UpdateWrapper.set("email", null) 可清空字段
批量更新updateBatchById(list)每条按各自 id
增量操作setSql("age = age + 1")数值加减,避免并发覆盖

查(Select / Get / List) ​

操作方法说明
按 id 查selectById(id) / getById(id)查不到返回 null
查全部selectList(null) / list()
按条件查列表selectList(wrapper) / list(wrapper)最常用
查一条selectOne(wrapper) / getOne(wrapper)结果多于一条会报错
查计数selectCount(wrapper) / count(wrapper)返回 long
分页查selectPage(page, wrapper) / page(page, wrapper)需要分页插件
按 id 批量查selectBatchIds(ids) / listByIds(ids)对应 IN
只查指定列wrapper.select(User::getId, User::getName)减少传输量
链式查询lambdaQuery().eq(...).list()不用 new wrapper
java
// 链式查询示例
userService.lambdaQuery()
    .like(User::getName, "王")
    .gt(User::getAge, 18)
    .orderByDesc(User::getId)
    .page(new Page<>(1, 10));

对应关系 ​

BaseMapperIService
insertsave
deleteByIdremoveById
updateByIdupdateById
selectByIdgetById
selectListlist
selectPagepage

日常开发几乎只用 Service 层,save / removeById / updateById / list(wrapper) / page(page, wrapper) 就能覆盖大部分场景

Mapper ​

继承 BaseMapper 就有了全套 CRUD,不用写任何方法

java
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

Service ​

Service 层是 BaseMapper 的封装,提供批量操作和链式查询

java
public interface UserService extends IService<User> {
}

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

IService 和 BaseMapper 的关系

  • insert → save
  • deleteById → removeById
  • updateById → updateById
  • selectById → getById
  • selectList → list