条件构造器
入门程序里的使用,都是简单的 CRUD。
在实际的应用场景中,我们还需要使用更复杂的操作,MyBatis-Plus 也给我们提供了相应的支持。
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper)
,Wrapper
是 MyBatis-Plus 对动态 SQL 条件
的面向对象抽象
,其命名直观体现了它作为"条件构造(包装)器"的核心职责:用于构建复杂的数据库查询条件
。
Wrapper 类及其功能
Wrapper 类
允许开发者以链式调用
的方式构造查询条件
,无需编写繁琐的 SQL 语句,从而提高开发效率,并减少 SQL 注入的风险
。
以下是主要的 Wrapper 类及其功能:
AbstractWrapper
:- 这是一个
抽象基类
,提供了所有 Wrapper 类
共有的方法
和属性
。 - 它定义了
条件构造
的基本逻辑
,包括字段(column)
、值(value)
、操作符(condition)
等。 - 所有的
QueryWrapper
、UpdateWrapper
、LambdaQueryWrapper
和LambdaUpdateWrapper
都继承自AbstractWrapper
。
- 这是一个
QueryWrapper
:- 专门用于
构造查询条件
,支持基本的等于
、不等于
、大于
、小于
等各种常见操作。 - 它允许你以
链式调用
的方式添加多个查询条件
,并且可以组合使用and
和or
逻辑。
- 专门用于
UpdateWrapper
:- 用于
构造更新条件
,可以在更新数据时指定条件
。 - 与
QueryWrapper
类似,它也支持链式调用
和逻辑组合
。 - 使用
UpdateWrapper
可以在不创建实体对象
的情况下,直接设置更新字段和条件
。
- 用于
LambdaQueryWrapper
:- 这是一个
基于 Lambda 表达式
的查询条件构造器
。 - 它通过 Lambda 表达式来
引用实体类的属性
,从而避免了硬编码字段名
。 - 这种方式提高了代码的
可读性
和可维护性
,尤其是在字段名可能发生变化
的情况下。
- 这是一个
-
LambdaUpdateWrapper
:-
类似于
LambdaQueryWrapper
。 -
LambdaUpdateWrapper
是基于Lambda 表达式
的更新条件构造器
。 -
它允许你使用
Lambda 表达式
来指定更新字段和条件
,同样避免了硬编码字段名
的问题。
-
QueryWrapper
QueryWrapper
并不只用于查询语句,无论是修改、删除、查询,都可以使用 QueryWrapper
来构建查询条件。
构造查询语句
通过条件构造器 QueryWrapper 完成下述 SQL 查询:
SELECT id, username, password, age
FROM user_info
WHERE age = 18 AND username
LIKE "%min%"
接下来,我们需要使用 MyBatis-Plus提供的条件构造器 QueryWrapper;
并且指定泛型参数,限定为UserInfo
实体类(通过UserInfo泛型,完成实体类
与表字段
的隐式映射
):
链式调用
条件构造操作(连着调用三个方法,属于链式调用):
.select("id,username,password,age")
→ 字段投影(指定查询列).eq("age",18)
→ 等值条件(WHERE age = 18).like("username", "min")
→ 模糊查询(WHERE username LIKE ‘%min%’)
调用 MyBatis-plus 提供的 BaseMapper 接口的内置方法 selectList() ,来根据构造好的条件查找表 user_info 符合构造条件的记录:
打印查询到的结果:
MyBatis-Plus 自动映射表名的机制
在使用 MyBatis-Plus 的 selectByCondition()
方法时,我们发现:
- 不需要显式指定
FROM 表名
- 却能准确查询到
user_info
表的数据
这与传统 MyBatis 的以下方式形成对比:
- 注解方式需要写
@Select("SELECT * FROM user_info")
- XML 方式需要写
<select id="..." resultMap="...">SELECT * FROM user_info</select>
思考:
- MyBatis-Plus 是如何实现:
自动确定查询的目标表名
? - MyBatis-Plus 是通过什么机制将
实体类
与数据库
表进行关联?
在 MyBatis-Plus 中,查询的表名
user_info
是通过以下方式隐式指定的:
完整逻辑链:
QueryWrapper<UserInfo>
→ 通过泛型找到UserInfo
实体类 → 通过实体类的@TableName
或默认规则确定表名 → 生成SELECT ... FROM user_info ...
运行对应测试类方法,观察打印日志:
MyBatis-Plus 字段名与属性名映射不一致导致查询报错
MyBatis-Plus 在字段映射时,还有一个注意事项,煮啵用一个具体的例子解释:
假设实体类和数据库表是这样定义的:
@Data
@TableName("user_info")
public class UserInfo {
@TableField(value = "user_name") // 数据库字段是user_name
private String username; // 实体属性是username
private Integer age;
}
当你这样查询时:
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.select("username, age") // 这里写的是实体属性名
.eq("age", 18);
生成的SQL会是:
SELECT username, age FROM user_info WHERE age = 18
但数据库实际没有username
字段(只有user_name
字段),这时就会报错字段不存在。
为什么会这样?
-
默认行为
不指定select时,MP会自动给字段加别名:SELECT user_name AS username, age FROM user_info
-
指定select后的变化
当手动指定select("username,age")
后:- MP会直接使用你写的字段名,不再自动转换
- 但数据库实际需要的是
user_name
而非username
解决字段名映射不一致的方法
方案 | 示例代码 | 优点 | 缺点 |
---|---|---|---|
自定义SQL | @Select("SELECT user_name, age FROM user_info") | 最灵活 | 失去MP的便捷性 |
保持属性名与字段名一致 | 实体类改成:private String user_name; | 最简单 | 破坏Java命名规范 |
不指定select字段 | 去掉wrapper.select() | 自动处理别名 | 无法控制返回字段 |
使用LambdaQueryWrapper | wrapper.select(UserInfo::getUsername, UserInfo::getAge) | 类型安全 | 需要MP 3.0+ |
推荐使用方案4(Lambda写法):
LambdaQueryWrapper<UserInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.select(UserInfo::getUsername, UserInfo::getAge)
.eq(UserInfo::getAge, 18);
这样会生成正确的SQL:
SELECT user_name, age FROM user_info WHERE age = 18
因为Lambda表达式能自动识别:
- 方法引用
UserInfo::getUsername
→ 对应@TableField("user_name")
- 完全避免字段名硬编码问题
构造更新语句
更新:完成下述 SQL 查询。
UPDATE user_info SET delete_flag=? WHERE age < 20
测试代码:
执行测试类方法,观察打印日志:
刷新数据库表,确认数据是否更新:
SQL 比较运算符
缩写 | 全称 | 含义 | SQL等价 | 示例 |
---|---|---|---|---|
lt | less than | 小于 | < | lt("age",18) → age<18 |
le | less than or equal to | 小于等于 | <= | le("age",18) → age<=18 |
gt | greater than | 大于 | > | gt("score",90) → score>90 |
ge | greater than or equal to | 大于等于 | >= | ge("price",100) → price>=100 |
eq | equals | 等于 | = | eq("name","John") → name='John' |
ne | not equals | 不等于 | <>或!= | ne("status",0) → status<>0 |
说明:
- 所有方法均支持链式调用(如
.eq("status",1).gt("age",18)
)。 - Lambda 表达式写法(如
lt(User::getAge, 18)
)可避免字段名硬编码。
构造删除语句
删除:完成下述 SQL 查询。
DELETE FROM user_info WHERE age = 18
测试代码:
运行对应测试类方法,观察打印日志:
所以无论查找、更新、删除相关的 SQL 语句,只要有 where 条件,都可以使用 QureyWrapper 来构造 SQL 语句;