Mybatis-Plus

11055 字
28 分钟

如果说空白行隔开了概念,靠近的代码行则暗示了它们之间的紧密关系。

官网:MyBatis-Plus 🚀 为简化开发而生

MyBatis-Plus

Mybatis-Plus采取约定大于配置的思想,按照约定进行定义,即可省去大量繁琐的代码。

MyBatis-Plus 是 MyBatis 的增强工具,简化了 CRUD 操作,减少了 XML 配置。

步骤

1. Mybatis-Puls起步依赖

MyBatis-Plus 提供了 Starter ,可一次性引入 MyBatis 与 MyBatisPlus 的核心能力。

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

2. 定义Mapper

MyBatis-Plus 特性

  • 通过继承 BaseMapper<T>,自动获得大量通用 CRUD 方法。
  • 无需手写 XML 或实现类即可直接使用。

定义方式:

public interface UserMapper extends BaseMapper<User> {
}
  • UserMapper :自定义的 Mapper 接口。
  • BaseMapper<user> :泛型指定实体类 User,自动绑定该实体的 CRUD 操作。
  • 通过 BaseMapper<t> 接口,自动获得单表的常用 CRUD 方法,无需手写 SQL。

BaseMapper常用方法

分类方法签名说明
Create(新增)int insert(T entity)插入一条记录,返回影响行数
Update(更新)int updateById(T entity)根据 ID 更新记录
int update(T entity, Wrapper<T> updateWrapper)根据条件更新记录
Delete(删除)int deleteById(Serializable id)根据 ID 删除记录
int deleteByMap(Map<String, Object> columnMap)根据字段条件 Map 删除记录
int delete(Wrapper<T> queryWrapper)根据条件删除记录
int deleteBatchIds(Collection<? extends Serializable> idList)批量删除(根据 ID 集合)
Read(查询)T selectById(Serializable id)根据 ID 查询单条记录
List<T> selectBatchIds(Collection<? extends Serializable> idList)批量查询(根据 ID 集合)
List<T> selectByMap(Map<String, Object> columnMap)根据字段条件 Map 查询记录
T selectOne(Wrapper<T> queryWrapper)根据条件查询单条记录
Long selectCount(Wrapper<T> queryWrapper)根据条件统计记录数
List<T> selectList(Wrapper<T> queryWrapper)根据条件查询列表
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)分页查询

常见注解

官方文档:注解配置 | MyBatis-Plus

MybatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。

MybatisPlus约定配置

以我们上面的接口为例:

public interface UserMapper extends BaseMapper<User> {
}

MybatisPlus通过反射可以获取User类的定义,并通过约定配置进行自动配置

假设User类如下:

@Data
public class User {
    private String username;
    private String password;
    private Integer age;
    private String sex;       // 或 gender
    private String phone;
    private String email;
    private Integer status;   // 状态字段
    private Double balance;   // 余额
    private Date createTime;
    private Date updateTime;
}

约定配置:

  • 类名驼峰转下划线作为表名。(表名为 user)
  • 名为id的字段作为主键。
  • 变量名驼峰转下划线作为表的字段名。(createTime -> create_time)

自定义配置

@TableName——指定表名

显式指定实体类对应的数据库表名,覆盖 MyBatis-Plus 的默认表名推导规则(类名 → 下划线 → 小写)。

@TableName("t_user")
public class User { ... }

@TableId

标识实体类中的主键字段,并可指定主键生成策略。

@TableId(value = "id", type = IdType.AUTO)
private Long id;

常见主键类型(IdType 枚举):

枚举值说明适用场景注意事项
AUTO数据库自增MySQL 等支持自增主键的数据库数据库字段需设置自增;插入时主键字段可为 null
NONE未设置主键类型(默认跟随全局配置)不想在实体类上单独指定策略依赖全局 global-config.db-config.id-type 配置
INPUT手动输入主键主键由业务方生成(如外部系统传入)插入前必须手动设置主键值,否则报错
ASSIGN_ID雪花算法生成全局唯一 ID(Long 类型)分布式系统、无数据库自增需求默认策略(Long 类型主键);不依赖数据库自增

易错点

  • 数据库主键非自增却使用 AUTO,会导致插入失败。
  • 忘记标注主键,分页、更新、删除等操作可能异常。

@TableField

作用

  • 显式指定实体字段与数据库列的映射关系。
  • 控制字段是否参与 SQL 操作(插入、更新、查询)。
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
属性名类型说明常见取值 / 示例注意事项
valueString指定数据库列名"create_time"当字段名与列名不一致时必须指定
existboolean是否为数据库表字段true(默认)、falsefalse 表示该字段不参与映射(如业务计算字段)
selectboolean查询时是否返回该字段true(默认)、falsefalse 可用于敏感字段(如密码)在查询时不返回
updateString更新时的 SQL 片段"NOW()"可用于更新时自动赋值,如更新时间字段

特殊情况:

  • 成员变量名以is开头,且是布尔值。如isMarried,默认数据库字段名为:married

  • 与SQL语言关键字重名。如order

    @TableField(value = "`order`")
    private String order;

常见配置

官方文档:使用配置 | MyBatis-Plus

基础配置:(mybatis-plus根节点)

mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po
  mapper-locations: "classpath:/mapper/**/*.xml"
配置项作用笔记关联 / 说明
type-aliases-package指定实体类所在包,自动注册别名(类名首字母小写)在 XML 中可直接用别名代替全类名,例如 user
mapper-locations指定 Mapper XML 文件位置(支持通配符)对应 UserMapper.xml 等自定义 SQL 文件路径

Mybatis核心配置(configuration节点)

mybatis-plus:
    configuration:
        map-underscore-to-camel-case: true
        cache-enabled: false
配置项作用笔记关联 / 说明
map-underscore-to-camel-case开启下划线 ↔ 驼峰自动映射例如 create_timecreateTime,布尔字段 isMarriedmarried
cache-enabled是否开启二级缓存(默认 true)分布式环境建议关闭,避免缓存不一致

全局配置(global-config.db-config)

mybatis-plus:
    global-config:
    db-config:
        id-type: assign_id
        update-strategy: not_null
配置项作用笔记关联 / 说明
id-type全局主键策略(IdType 枚举)例如 assign_id = 雪花算法 Long ID,可被 @TableId 覆盖
update-strategy字段更新策略not_null 表示只更新非空字段,避免 null 覆盖数据库值

💡 知识闭环

  1. 实体类 ↔ 表映射 :由 map-underscore-to-camel-case + 注解控制
  2. 主键策略 :全局 id-type + @TableId
  3. 更新行为 :全局 update-strategy + @TableField
  4. SQL 文件加载mapper-locations 决定 XML 能否被扫描到

核心功能

条件构造器-Wrapper

Wrapper是Mybatis-Plus提供的动态SQL条件构造器,用链式调用的方法构建复杂WHERE条件(以及SET更新字段),避免手写SQL,提高可维护性,并减少SQL注入的风险。

主要类型对比:

1757488048014

构造器用途特点适用场景
QueryWrapper<T>查询条件字段名用字符串简单场景,字段名固定
LambdaQueryWrapper<T>查询条件字段名用方法引用(如 User::getName推荐首选,避免硬编码,重构安全
UpdateWrapper<T>更新条件支持 set + 条件无需实体对象参与更新
LambdaUpdateWrapper<T>更新条件方法引用 + 更新推荐首选,安全更新字段

QueryWrapper:在 AbstractWrapper父类之上拓展了查询select的方法。

UpdateWrapper:在父类之上拓展了更新set的方法。

常用条件方法:

方法说明示例
eq等于.eq("status", 1)status = 1
ne不等于.ne(User::getName, "张三")
gt / ge大于 / 大于等于.gt("age", 18)
lt / le小于 / 小于等于.le(User::getAge, 30)
between区间.between("age", 20, 30)
like / likeLeft / likeRight模糊匹配.like(User::getName, "张")
isNull / isNotNull空值判断.isNotNull("email")
in / notIn集合匹配.in("id", Arrays.asList(1,2,3))
orderByAsc / orderByDesc排序.orderByDesc("create_time")
and / or逻辑组合.and(w -> w.eq("status", 1).lt("age", 30))
allEq批量等于.allEq(map, true)nullIS NULL

示例1:QueryWrapper

1757488616235

MybatisPlus示例:

void testQueryWrapper() {
    // 查询语句条件
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
        .select("id", "username", "info", "balance")
        .like("username", "o")
        .ge("balance", 1000);

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

示例2:UpdateWrapper

1757489036051

MybatisPlus示例:

@Test
void testUpdateWrapper() {
    List`<Long>` ids = List.of(1L, 2L, 4L);

    UpdateWrapper`<User>` wrapper = new UpdateWrapper`<User>`()
        .setSql("balance = balance - 200")
        .in("id", ids);

    userMapper.update(null, wrapper);
}

示例3:LambdaUpdateWrapper

LambdaWrapper表达式中属性的名字不采用硬编码,而使用对象方法来获取字段名。

void testLambdaQueryWrapper() {
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.select(User::getId, User::getUsername, User::getBalance)
           .eq(User::getUsername, "o")
           .ge(User::getBalance, 1000);
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

自定义SQL

上面的示例2中,我们直接将SQL语句硬编码在业务代码中:balance = balance - 200,在企业开发规范中,这样硬编码在业务代码中的语句通常是不符合规范的,我们可以使用自定义SQL来解决这个问题。

自定义SQL允许我们使用基本的Mybatis来书写基本的sql语句,却使用Mybatis-plus来的Wrapper来配置where条件。简化了mybatis配置文件中where语句的书写,且将其移动至业务逻辑中。

步骤

  1. 构建WHERE条件
  • 使用 LambdaQueryWrapper 可以通过 方法引用 安全地指定字段,避免硬编码字符串。

  • 示例:

    List<Long> ids = List.of(1L, 2L, 4L);
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
        .in(User::getId, ids);
    userMapper.updateBalanceByIds(wrapper, amount);
  1. Mapper接口参数传递
  • 在 Mapper 方法中使用 @Param 注解传递参数。
  • 示例:
    void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper,
                            @Param("amount") BigDecimal amount);
  • ew 是 MyBatisPlus 约定的参数名,用于在 XML 中引用 LambdaQueryWrapper
  • 其他参数(如 amount)可按需命名。
  1. XML中使用 ew.customSqlSegment
  • 在 XML 中通过 ${ew.customSqlSegment} 引入动态 SQL 条件。

  • 示例:

    <update id="updateBalanceByIds">
      UPDATE user
      SET balance = balance + #{amount}
      ${ew.customSqlSegment}
    </update>

Service接口

MyBatis-Plus 提供了一个通用的 Service 层接口 IService<t></t>,以及默认实现类 ServiceImpl<m, t=""></m,>,用于简化业务层的 CRUD 操作。

1757578211301

基本使用

使用步骤

  1. 自定义接口继承 IService<T>

    public interface IUserService extends IService<User> {
        // 可以添加自定义业务方法
    }
  2. 实现类继承ServiceImpl<M,T>

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
        // 可以重写或扩展方法
    }

然后我们就可以直接使用ServiceImpl中继承的默认实现方法。

常用方法

分类方法说明
新增save(T entity)插入单条记录
saveBatch(Collection<T> list)批量插入
更新updateById(T entity)根据 ID 更新
update(T entity, Wrapper<T> updateWrapper)条件更新
删除removeById(Serializable id)根据 ID 删除
remove(Wrapper<T> queryWrapper)条件删除
查询getById(Serializable id)根据 ID 查询
list()查询全部
list(Wrapper<T> queryWrapper)条件查询
page(IPage<T> page, Wrapper<T> queryWrapper)分页查询
统计count()统计总数
count(Wrapper<T> queryWrapper)条件统计
链式调用lambdaQuery()链式条件查询
lambdaUpdate()链式条件更新

IService的Lambda查询

LambdaQuery

lambdaQuery 是 MyBatis-Plus 提供的 基于 Lambda 表达式的条件构造器 ,它的核心优势是:

  • 类型安全 :通过方法引用(如 User::getName)避免了硬编码字段名,字段改名时编译期即可发现问题。
  • 链式调用 :支持流式 API,条件拼接更直观。
  • 可读性强 :条件语义清晰,减少 SQL 拼写错误。

lambdaQuerylambdaQueryWrapper的基础上增加了一些常用的终端操作,可以直接使用构造的条件进行查询。

常见终端操作
方法返回类型作用常见用法示例备注
list()List<T>查询多条记录.list()最常用,返回集合,若无结果返回空集合
one()T查询单条记录.one()若结果多于 1 条会抛异常,适合唯一性查询
oneOpt()Optional<T>查询单条记录(可选).oneOpt()避免空指针,Java 8+ 推荐
getOne(boolean throwEx)T查询单条记录(可选限制).getOne(false)throwEx=false 时多条返回首条
count()long统计数量.count()常用于分页前的总数统计
exists()boolean判断是否存在记录.exists()count() > 0 更高效
listObjs()List<Object>查询单列数据.listObjs()只取第一列的值,减少内存占用
listObjs(Function<T,?> mapper)List<R>查询单列并映射.listObjs(User::getName)直接返回指定字段集合
listMaps()List<Map<String,Object>>查询多条记录(Map).listMaps()适合动态列或不想映射实体类时
page(Page<T>)IPage<T>分页查询.page(new Page<>(1,10))结合分页插件使用
pageMaps(Page<Map<String,Object>>)IPage<Map<String,Object>>分页查询(Map).pageMaps(new Page<>(1,10))返回 Map 形式分页结果

对比示例:

mybatis:

<select id="queryUsers" resultType="com.example.User">
    SELECT
        id,
        username,
        status,
        balance
    FROM
        user
    <where>
        <if test="name != null">
            AND username LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
        <if test="minBalance != null">
            AND balance >= #{minBalance}
        </if>
        <if test="maxBalance != null">
            AND balance <= #{maxBalance}
        </if>
    </where>
</select>

mybatis.plus:

@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
    return lambdaQuery()
        .like(name != null, User::getUsername, name)  //第一个参数为生效条件
        .eq(status != null, User::getStatus, status)
        .ge(minBalance != null, User::getBalance, minBalance)
        .le(maxBalance != null, User::getBalance, maxBalance)
        .list();
}

动态条件拼接:利用 condition参数避免无效条件。

and和or的使用方式

在MyBatis-Plus的 LambdQuaryWrapperlambdaQuery()中:

  • .and(Consumer<Wrapper>):用于构造括号包裹的AND条件组。
  • .or(Consumer<Wrapper>):用于构造括号包裹的OR条件组。
  • .or():用于直接插入一个OR连接符号。

示例:

lambdaQuery()
    .and(q -> q.eq(User::getStatus, 1).gt(User::getBalance, 1000))
    .or(q -> q.eq(User::getStatus, 0).lt(User::getBalance, 500))
    .list();

SQL:

WHERE (status = 1 AND balance > 1000) OR (status = 0 AND balance < 500)

总结:

lambdaQuery 条件组合
 ├── 默认 AND:连续调用条件方法
 ├── or():插入 OR(无括号)
 ├── or(q -> {...}):OR 条件组(推荐)
 ├── and(q -> {...}):AND 条件组(推荐)
 └── 嵌套组合:实现复杂逻辑表达式

LambdaUpdate

lambdaUpdate是MyBatis-Plus提供的基于Lambda表达式的更新构造器。它的作用类似于 lambdaQuery,但是用于UPDARE操作。

链式调用:

boolean updated = userService.lambdaUpdate()
			     .set(User::getUsername, "张三")   //更新字段
			     .set(User::getBalance, 2000)	//支持同时更新多个字段
			     .eq(User::getId, 1001)		//更新条件
			     .update();				//执行更新

生成SQL:

UPDATE user SET username = '张三', balance = 2000 WHERE id = 1001

动态条件更新:

boolean update = userService.lambdaUpdate()
			.set(name != null, User::getUsername, name)
			.set(minBalance != null, User:getBalance, minBalance)
			.eq(User.getStatus, 1)
			.update();

只有当 nameminBalance非空时,才会拼接对应的SET语句。

删除操作:

lambdaUpdate也可以来构造删除条件:

boolean removed = userService.lambdaUpdate()
    .eq(User::getStatus, 0)
    .remove();

生成sql:

DELETE FROM user WHERE status = 0

批量新增

循环单个插入

@Test
void testSaveOneByOne() {
    long b = System.currentTimeMillis();
    for (int i = 1; i <= 100000; i++) {
        userService.save(buildUser(i));
    }
    long e = System.currentTimeMillis();
    System.out.println("耗时: " + (e - b));
}

这种方式插入数据非常慢,原因如下:

  • 每次调用都是一次独立的数据库事务/连接
  • 网络往返次数过多
    • 10万次网络请求
  • ORM框架的额外开销

测试耗时:20万ms

批处理

@Test
public void testSaveBatch() {
    // 模拟生成100000个用户,插入100000条记录到数据库
    List<User> list = new ArrayList<>(100000);
    long b = System.currentTimeMillis();
    for (int i = 1; i < 100000; i++) {
        // 构造用户
        User user = buildUser(i);
        list.add(user);
        // 每1000条记录插入一次
        if (i % 1000 == 0) {
            userService.saveBatch(list);
            list.clear();
        }
    }
    long e = System.currentTimeMillis();
    System.out.println("耗时:" + (e - b));
}

测试耗时:2万ms

为什么快:

  • 减少数据库交互次数。
    • 100次网络请求
  • 降低事务的开销
    • 100次COMMIT
  • SQL优化器更高效
    • 合并成一条 INSERT...VALUES(...),(...),...数据库只会解析一次SQL,执行计划也只生成一次。
  • 较少QRM额外的开销

1758108670644

要让Mybatis=Plus开启SQL优化器,需要开启MySql的 rewriteBatchedStatements参数。

开启方法:application.yml

在jdbc连接后拼接参数:

jdbc:mysql://localhost:3306/tlias&rewriteBatchedStatements=true