Mybatis-Plus

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

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

MyBatis-Plus

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

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

步骤

1. Mybatis-Puls起步依赖

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

1
2
3
4
5
<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 或实现类即可直接使用。

定义方式:

1
2
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约定配置

以我们上面的接口为例:

1
2
public interface UserMapper extends BaseMapper<User> {
}

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

假设User类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@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 的默认表名推导规则(类名 → 下划线 → 小写)。

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

@TableId

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

1
2
@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 操作(插入、更新、查询)。
1
2
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
属性名 类型 说明 常见取值 / 示例 注意事项
value String 指定数据库列名 "create_time" 当字段名与列名不一致时必须指定
exist boolean 是否为数据库表字段 true(默认)、false false 表示该字段不参与映射(如业务计算字段)
select boolean 查询时是否返回该字段 true(默认)、false false 可用于敏感字段(如密码)在查询时不返回
update String 更新时的 SQL 片段 "NOW()" 可用于更新时自动赋值,如更新时间字段

特殊情况:

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

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

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

常见配置

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

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

1
2
3
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节点)

1
2
3
4
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)

1
2
3
4
5
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示例:

1
2
3
4
5
6
7
8
9
10
11
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示例:

1
2
3
4
5
6
7
8
9
10
@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表达式中属性的名字不采用硬编码,而使用对象方法来获取字段名。

1
2
3
4
5
6
7
8
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 可以通过 方法引用 安全地指定字段,避免硬编码字符串。

  • 示例:

    1
    2
    3
    4
    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 注解传递参数。
  • 示例:
    1
    2
    void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper,
    @Param("amount") BigDecimal amount);
  • ew 是 MyBatisPlus 约定的参数名,用于在 XML 中引用 LambdaQueryWrapper
  • 其他参数(如 amount)可按需命名。
  1. XML中使用 ew.customSqlSegment
  • 在 XML 中通过 ${ew.customSqlSegment} 引入动态 SQL 条件。

  • 示例:

    1
    2
    3
    4
    5
    <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>

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

    1
    2
    3
    4
    @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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<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:

1
2
3
4
5
6
7
8
9
@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连接符号。

示例:

1
2
3
4
5
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:

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

总结:

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

LambdaUpdate

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

链式调用:

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

生成SQL:

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

动态条件更新:

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

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

删除操作:

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

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

生成sql:

1
DELETE FROM user WHERE status = 0

批量新增

循环单个插入

1
2
3
4
5
6
7
8
9
@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

批处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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连接后拼接参数:

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

Mybatis-Plus
http://blog.ulna520.com/2025/09/08/Mybatis-Plus_20250908_222043/
Veröffentlicht am
September 8, 2025
Urheberrechtshinweis