Mybatis-Plus
如果说空白行隔开了概念,靠近的代码行则暗示了它们之间的紧密关系。
MyBatis-Plus
Mybatis-Plus采取约定大于配置的思想,按照约定进行定义,即可省去大量繁琐的代码。
MyBatis-Plus 是 MyBatis 的增强工具,简化了 CRUD 操作,减少了 XML 配置。
步骤
1. Mybatis-Puls起步依赖
MyBatis-Plus 提供了 Starter ,可一次性引入 MyBatis 与 MyBatisPlus 的核心能力。
1 |
|
2. 定义Mapper
MyBatis-Plus 特性 :
- 通过继承
BaseMapper<T>
,自动获得大量通用 CRUD 方法。 - 无需手写 XML 或实现类即可直接使用。
定义方式:
1 |
|
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 |
|
MybatisPlus通过反射可以获取User类的定义,并通过约定配置进行自动配置:
假设User类如下:
1 |
|
约定配置:
- 类名驼峰转下划线作为表名。(表名为
user
) - 名为id的字段作为主键。
- 变量名驼峰转下划线作为表的字段名。(
createTime
->create_time
)
自定义配置
@TableName
——指定表名
显式指定实体类对应的数据库表名,覆盖 MyBatis-Plus 的默认表名推导规则(类名 → 下划线 → 小写)。
1 |
|
@TableId
标识实体类中的主键字段,并可指定主键生成策略。
1 |
|
常见主键类型(IdType 枚举):
枚举值 | 说明 | 适用场景 | 注意事项 |
---|---|---|---|
AUTO |
数据库自增 | MySQL 等支持自增主键的数据库 | 数据库字段需设置自增;插入时主键字段可为 null |
NONE |
未设置主键类型(默认跟随全局配置) | 不想在实体类上单独指定策略 | 依赖全局 global-config.db-config.id-type 配置 |
INPUT |
手动输入主键 | 主键由业务方生成(如外部系统传入) | 插入前必须手动设置主键值,否则报错 |
ASSIGN_ID |
雪花算法生成全局唯一 ID(Long 类型) | 分布式系统、无数据库自增需求 | 默认策略(Long 类型主键);不依赖数据库自增 |
易错点
- 数据库主键非自增却使用
AUTO
,会导致插入失败。 - 忘记标注主键,分页、更新、删除等操作可能异常。
@TableField
作用
- 显式指定实体字段与数据库列的映射关系。
- 控制字段是否参与 SQL 操作(插入、更新、查询)。
1 |
|
属性名 | 类型 | 说明 | 常见取值 / 示例 | 注意事项 |
---|---|---|---|---|
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 |
|
配置项 | 作用 | 笔记关联 / 说明 |
---|---|---|
type-aliases-package |
指定实体类所在包,自动注册别名(类名首字母小写) | 在 XML 中可直接用别名代替全类名,例如 user |
mapper-locations |
指定 Mapper XML 文件位置(支持通配符) | 对应 UserMapper.xml 等自定义 SQL 文件路径 |
Mybatis核心配置(configuration
节点)
1 |
|
配置项 | 作用 | 笔记关联 / 说明 |
---|---|---|
map-underscore-to-camel-case |
开启下划线 ↔ 驼峰自动映射 | 例如 create_time ↔ createTime ,布尔字段 isMarried ↔ married |
cache-enabled |
是否开启二级缓存(默认 true) | 分布式环境建议关闭,避免缓存不一致 |
全局配置(global-config.db-config
)
1 |
|
配置项 | 作用 | 笔记关联 / 说明 |
---|---|---|
id-type |
全局主键策略(IdType 枚举) |
例如 assign_id = 雪花算法 Long ID,可被 @TableId 覆盖 |
update-strategy |
字段更新策略 | not_null 表示只更新非空字段,避免 null 覆盖数据库值 |
💡 知识闭环
- 实体类 ↔ 表映射 :由
map-underscore-to-camel-case
+ 注解控制 - 主键策略 :全局
id-type
+@TableId
- 更新行为 :全局
update-strategy
+@TableField
- SQL 文件加载 :
mapper-locations
决定 XML 能否被扫描到
核心功能
条件构造器-Wrapper
Wrapper是Mybatis-Plus提供的动态SQL条件构造器,用链式调用的方法构建复杂WHERE条件(以及SET更新字段),避免手写SQL,提高可维护性,并减少SQL注入的风险。
主要类型对比:
构造器 | 用途 | 特点 | 适用场景 |
---|---|---|---|
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) (null 转 IS NULL ) |
示例1:QueryWrapper
MybatisPlus示例:
1 |
|
示例2:UpdateWrapper
MybatisPlus示例:
1 |
|
示例3:LambdaUpdateWrapper
LambdaWrapper表达式中属性的名字不采用硬编码,而使用对象方法来获取字段名。
1 |
|
自定义SQL
上面的示例2中,我们直接将SQL语句硬编码在业务代码中:balance = balance - 200
,在企业开发规范中,这样硬编码在业务代码中的语句通常是不符合规范的,我们可以使用自定义SQL来解决这个问题。
自定义SQL允许我们使用基本的Mybatis来书写基本的sql语句,却使用Mybatis-plus来的Wrapper来配置where条件。简化了mybatis配置文件中where语句的书写,且将其移动至业务逻辑中。
步骤
- 构建WHERE条件
使用
LambdaQueryWrapper
可以通过 方法引用 安全地指定字段,避免硬编码字符串。示例:
1
2
3
4List<Long> ids = List.of(1L, 2L, 4L);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.in(User::getId, ids);
userMapper.updateBalanceByIds(wrapper, amount);
- Mapper接口参数传递
- 在 Mapper 方法中使用
@Param
注解传递参数。 - 示例:
1
2void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper,
@Param("amount") BigDecimal amount);
ew
是 MyBatisPlus 约定的参数名,用于在 XML 中引用LambdaQueryWrapper
。- 其他参数(如
amount
)可按需命名。
- 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 操作。
基本使用
使用步骤
自定义接口继承
IService<T>
1
2
3public interface IUserService extends IService<User> {
// 可以添加自定义业务方法
}实现类继承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 拼写错误。
lambdaQuery
在 lambdaQueryWrapper
的基础上增加了一些常用的终端操作,可以直接使用构造的条件进行查询。
常见终端操作
方法 | 返回类型 | 作用 | 常见用法示例 | 备注 |
---|---|---|---|---|
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 |
|
mybatis.plus:
1 |
|
动态条件拼接:利用
condition
参数避免无效条件。
and和or的使用方式
在MyBatis-Plus的 LambdQuaryWrapper
或 lambdaQuery()
中:
.and(Consumer<Wrapper>)
:用于构造括号包裹的AND条件组。.or(Consumer<Wrapper>)
:用于构造括号包裹的OR条件组。.or()
:用于直接插入一个OR连接符号。
示例:
1 |
|
SQL:
1 |
|
总结:
1 |
|
LambdaUpdate
lambdaUpdate是MyBatis-Plus提供的基于Lambda表达式的更新构造器。它的作用类似于 lambdaQuery
,但是用于UPDARE操作。
链式调用:
1 |
|
生成SQL:
1 |
|
动态条件更新:
1 |
|
只有当
name
或minBalance
非空时,才会拼接对应的SET语句。
删除操作:
lambdaUpdate
也可以来构造删除条件:
1 |
|
生成sql:
1 |
|
批量新增
循环单个插入
1 |
|
这种方式插入数据非常慢,原因如下:
- 每次调用都是一次独立的数据库事务/连接
- 网络往返次数过多
- 10万次网络请求
- ORM框架的额外开销
测试耗时:20万ms
批处理
1 |
|
测试耗时:2万ms
为什么快:
- 减少数据库交互次数。
- 100次网络请求
- 降低事务的开销
- 100次COMMIT
- SQL优化器更高效
- 合并成一条
INSERT...VALUES(...),(...),...
数据库只会解析一次SQL,执行计划也只生成一次。
- 合并成一条
- 较少QRM额外的开销
要让Mybatis=Plus开启SQL优化器,需要开启MySql的
rewriteBatchedStatements
参数。开启方法:
application.yml
在jdbc连接后拼接参数:
1
jdbc:mysql://localhost:3306/tlias&rewriteBatchedStatements=true