目录
[1. 动态SQL](#1. 动态SQL)
[1.1 if标签](#1.1 if标签)
[1.2 trim标签](#1.2 trim标签)
[1.2.1 if标签的安全隐患](#1.2.1 if标签的安全隐患)
[1.2.2 解决方案--使用trim标签](#1.2.2 解决方案--使用trim标签)
[1.3 where标签](#1.3 where标签)
[1.3.1 不使用where标签的情况](#1.3.1 不使用where标签的情况)
[1.3.2 使用where标签](#1.3.2 使用where标签)
[1.4 set标签](#1.4 set标签)
[1.5 foreach标签](#1.5 foreach标签)
[1.6 include标签](#1.6 include标签)
[2. MyBatis-Plus使用](#2. MyBatis-Plus使用)
[2.1 MyBatis-Plus介绍](#2.1 MyBatis-Plus介绍)
[2.2 准备工作](#2.2 准备工作)
[2.1.1 数据准备](#2.1.1 数据准备)
[2.1.2 项目准备](#2.1.2 项目准备)
[2.3 编码](#2.3 编码)
[2.4 CRUD单元测试](#2.4 CRUD单元测试)
[3. MyBatis-Plus复杂操作](#3. MyBatis-Plus复杂操作)
[3.1 常见注解](#3.1 常见注解)
[3.1.1 @TableName](#3.1.1 @TableName)
[3.1.2 @TableField](#3.1.2 @TableField)
[3.1.3 @TableId](#3.1.3 @TableId)
[3.2 打印日志](#3.2 打印日志)
[3.3 条件构造器](#3.3 条件构造器)
[3.3.1 QueryWrapper](#3.3.1 QueryWrapper)
[3.3.2 UpdateWrapper](#3.3.2 UpdateWrapper)
[3.3.3 LambdaQueryWrapper](#3.3.3 LambdaQueryWrapper)
[3.3.4 LambdaUpdateWrapper](#3.3.4 LambdaUpdateWrapper)
[3.4 自定义SQL](#3.4 自定义SQL)
1. 动态SQL
动态 SQL 是Mybatis的强大特性之一,能够完成不同条件下不同的 sql 拼接 可以参考官方文档:
https://mybatis.net.cn/dynamic-sql.html
1.1 if标签
在注册用户的时候,可能会有这样一个问题,如下图所示

这个需求使用普通的sql语句无法完成
这个时候就需要使用动态标签来判断了,比如添加的时候出版社 publish 为非必填字段,具体实现如下
Mapper.xml实现:
UserInfoXMLMapper.java
java
void insertUserInfoByCondition(UserInfo userInfo);
UserInfoXMLMapperTest.java
java
@Test
void insertUserInfoByCondition() {
UserInfo userInfo = new UserInfo();
// userInfo.setAge(8);
userInfo.setUsername("xht");
userInfo.setPassword("aaa");
userInfo.setDeleteFlag(1);
userInfoMapperXML.insertUserInfoByCondition(userInfo);
}
UserInfoXMLMapper.xml
XML
<insert id="insertUserInfoByCondition">
insert into user_info(
id,
username,
password,
<if test="age!=null">
age,
</if>
<if test="gender!=null">
gender,
</if>
<if test="phone!=null">
phone,
</if>
delete_flag
)
values(
#{id},
#{username},
#{password},
<if test="age!=null">
#{age},
</if>
<if test="gender!=null">
#{gender},
</if>
<if test="phone!=null">
#{phone},
</if>
#{deleteFlag}
)
</insert>
使用if标签的字段可以不传入属性,看一下服务器的日志,发现如果在test文件中没有传入age,gender,phone的话,他们就不会出现在insert语句当中

1.2 trim标签
1.2.1 if标签的安全隐患
之前的插入用户功能,只是有一个 gender 字段可能是选填项,如果有多个字段,只是用if标签则会有很大的安全隐患。
比如,我们对上面xml中的语句稍作改变
UserInfoXMLMapper.xml
XML
<insert id="insertUserInfoByCondition">
insert into user_info(
id,
username,
password,
<if test="age!=null">
age,
</if>
<if test="gender!=null">
gender,
</if>
<if test="phone!=null">
phone,
</if>
)
values(
#{id},
#{username},
#{password},
<if test="age!=null">
#{age},
</if>
<if test="gender!=null">
#{gender},
</if>
<if test="phone!=null">
#{phone},
</if>
)
</insert>
UserInfoXMLMapperTest.java
java
@Test
void insertUserInfoByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("xht");
userInfo.setPassword("aaa");
userInfo.setAge(12);
// userInfo.setGender(2);
// userInfo.setPhone("123345");
userInfoMapperXML.insertUserInfoByCondition(userInfo);
}

这样,如果我们有些属性输入为空,那么,很有可能最后拼出来的sql语句是有问题的,我们查看一下这里的错误日志

1.2.2 解决方案--使用trim标签
一般考虑使用标签结合标签 ,对多个字段都采取动态生成的方式。
标签中有如下属性:
• prefix:表示整个语句块,以prefix的值作为前缀
• suffix:表示整个语句块,以suffix的值作为后缀
• prefixOverrides:表示整个语句块要去除掉的前缀
• suffixOverrides:表示整个语句块要去除掉的后缀
调整 UserInfoXMLMapper.xml 的插入语句为:
XML
<insert id="insertUserInfoByCondition">
insert into user_info
<trim prefix = "(" suffix=")" suffixOverrides=",">
id,
username,
password,
<if test="age!=null">
age,
</if>
<if test="gender!=null">
gender,
</if>
<if test="phone!=null">
phone
</if>
</trim>
values
<trim prefix = "(" suffix=")" suffixOverrides=",">
#{id},
#{username},
#{password},
<if test="age!=null">
#{age},
</if>
<if test="gender!=null">
#{gender},
</if>
<if test="phone!=null">
#{phone}
</if>
</trim>
</insert>
UserInfoXMLMapperTest.java不变:
java
@Test
void insertUserInfoByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("xht");
userInfo.setPassword("aaa");
userInfo.setAge(12);
// userInfo.setGender(2);
// userInfo.setPhone("123345");
userInfoMapperXML.insertUserInfoByCondition(userInfo);
}
查看服务器日志,发现执行成功

查看数据库,发现数据库更新数据正确

在以上 sql 动态解析时,会将第一个 部分做如下处理:
• 基于 prefix 配置,开始部分加上 (
• 基于 suffix 配置,结束部分加上 )
•多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于 suffixOverrides 配置去掉最后一个 ,
• 注意<if test="username!=null">中的 username 是传入对象的属性

1.3 where标签
1.3.1 不使用where标签的情况
UserInfoXMLMapper.java
java
List<UserInfo> selectByCondition(UserInfo userInfo);
UserInfoXMLMapper.xml
XML
<select id="selectByCondition" resultType="com.mxy.mybatisreviewdemo.model.UserInfo">
SELECT * FROM user_info
WHERE
age = #{age}
AND gender = #{gender}
AND delete_flag = #{deleteFlag}
</select>
UserInfoXMLMapperTest.java不变:
java
@Test
void selectByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setDeleteFlag(0);
userInfo.setAge(9);
userInfo.setGender(2);
userInfoMapperXML.selectByCondition(userInfo);
}

但这种固定的sql语句无法做到随时增减查询条件,在京东购物的网站里面的选择标签就需要这个动态sql语句
1.3.2 使用where标签
看下面这个场景,系统会根据我们的筛选条件, 动态组装where 条件

这种如何实现呢? 接下来我们看代码实现:
需求: 传入的用户对象,根据属性做where条件查询,用户对象中属性不为 null 的,都为查询条件. 如 username 为 "a",则查询条件为 where username="a"
UserInfoXMLMapper.javaUserInfoXMLMapper.java
java
List<UserInfo> selectByCondition(UserInfo userInfo);
UserInfoXMLMapper.xml
XML
<select id="selectByCondition" resultType="com.mxy.mybatisreviewdemo.model.UserInfo">
SELECT * FROM user_info
<where>
<if test="age!=null">
age = #{age}
</if>
<if test="gender!=null">
AND gender = #{gender}
</if>
<if test="deleteFlag!=null">
AND delete_flag = #{deleteFlag}
</if>
</where>
</select>
UserInfoXMLMapperTest.java:
java
@Test
void selectByCondition() {
UserInfo userInfo = new UserInfo();
// userInfo.setAge(9);
userInfo.setGender(2);
userInfo.setDeleteFlag(0);
userInfoMapperXML.selectByCondition(userInfo);
}

<where>只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的AND或 OR
以上标签也可以使用<trim prefix="where" prefixOverrides="and"> 替换, 但是此种情况下, 当子元素都没有内容时, where关键字也会保留
1.4 set标签
需求: 根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容.
接口定义: 根据传入的用户 id 属性,修改其他不为 null 的属性
UserInfoXMLMapper.java
java
Integer updateUserByCondition(UserInfo userInfo);
UserInfoXMLMapper.xml
XML
<update id="updateUserByCondition">
update user_info
<set>
<if test="username!=null">
username = #{username},
</if>
<if test="age!=null">
age = #{age},
</if>
<if test="deleteFlag!=null">
delete_flag = #{deleteFlag},
</if>
</set>
where id = #{id}
</update>
UserInfoXMLMapperTest.java
java
@Test
void updateUserByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setAge(9);
userInfo.setUsername("lisi");
userInfo.setAge(23);
userInfo.setId(7);
userInfoMapperXML.updateUserByCondition(userInfo);
}

我们这里没有传deleteFlag,因此,可以看出set标签也可以去除多余的","
<set>:动态的在SQL语句中插入set关键字,并会删掉额外的逗号. (用于update语句中) 以上标签也可以使用<trim prefix="set" suffixOverrides=",">替换
1.5 foreach标签
对集合进行遍历时可以使用该标签。标签有如下属性:
• collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
• item:遍历时的每一个对象
• open:语句块开头的字符串
• close:语句块结束的字符串
• separator:每次遍历之间间隔的字符串
需求: 根据多个userid, 删除用户数据
接口方法 UserInfoXMLMapper.java
java
void deleteByIds(List<Integer> ids);
UserInfoXMLMapper.xml
XML
<delete id="deleteByIds">
delete from user_info
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
UserInfoXMLMapperTest.java
java
@Test
void deleteByIds() {
List<Integer> list = new ArrayList<>();
list.add(6);
list.add(7);
userInfoMapperXML.deleteByIds(list);
}
查看服务器日志:

查看数据库,看到id为6,7的数据都已经被删除了

1.6 include标签
问题分析:
• 在xml映射文件中配置的SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码

接口方法 UserInfoXMLMapper.java
java
List<UserInfo> selectAll1();
List<UserInfo> selectAll2();
UserInfoXMLMapper.xml
XML
<select id="selectAll1" resultType="com.mxy.mybatisreviewdemo.model.UserInfo">
select
<include refid="allColumn"></include>
from user_info;
</select>
<select id="selectAll2" resultType="com.mxy.mybatisreviewdemo.model.UserInfo">
select
<include refid="allColumn"></include>
from user_info where id = 3;
</select>
我们可以对重复的代码片段进行抽取,将其通过 标签封装到一个SQL片段,然后再通过<include>标签进行引用
• <sql> :定义可重用的SQL片段
• <include>:通过属性refid,指定包含的SQL片段
UserInfoXMLMapperTest.java
java
@Test
void selectAll1() {
System.out.println(userInfoMapperXML.selectAll1());
}
@Test
void selectAll2() {
System.out.println(userInfoMapperXML.selectAll2());
}


2. MyBatis-Plus使用
2.1 MyBatis-Plus介绍
MyBatis-Plus(简称 MP) 是一个 MyBatis 的增强工具, 在 MyBatis 的基础上只做增强不做改变, 为简化开 发. 提高效率而生
特性:
• 润物无声: 只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑.
• 效率至上: 只需简单配置,即可快速进行单表 CRUD 操作,从而节省大量时间.
• 丰富功能: 代码生成、自动分页、逻辑删除、自动填充、拦截器等功能一应俱全.
• 广泛认可: 连续 5 年获得开源中国年度最佳开源项目殊荣,Github 累计 16K Star.
2.2 准备工作
2.1.1 数据准备
创建用户表, 并创建对应的实体类User
sql
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
USE mybatis_test;
-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`username` VARCHAR ( 127 ) NOT NULL,
`password` VARCHAR ( 127 ) NOT NULL,
`age` TINYINT ( 4 ) NOT NULL,
`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',
`phone` VARCHAR ( 15 ) DEFAULT NULL,
`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
`create_time` DATETIME DEFAULT now(),
`update_time` DATETIME DEFAULT now(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
2.1.2 项目准备
- 创建springboot工程 2. 添加MyBatis-Plus和MySQL依赖, 配置数据库连接信息
对于 Spring Boot3
需要在pom.xml中添加依赖
XML
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
配置数据库 application.yml文件, 配置内容如下:
spring:
application:
name: mybatis-review-demo
# ???????
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置打印 MyBatis日志
map-underscore-to-camel-case: true #配置驼峰自动转换
# 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件
mapper-locations: classpath:/static/*.xml
# 数据库连接配置
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
2.3 编码
创建实体类 UserInfo 实体类的属性名与表中的字段名一一对应
java
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
编写Mapper接口类
MybatisPlus提供了一个基础的 BaseMapper 接口,已经实现了单表的CRUD, 我们自定义的 Mapper只需要继承这个BaseMapper, 就无需自己实现单表CRUD了
BaseMapper.java
java
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int deleteById(T entity);
default int deleteByMap(Map<String, Object> columnMap) {
return this.delete((Wrapper)Wrappers.query().allEq(columnMap));
}
int delete(@Param("ew") Wrapper<T> queryWrapper);
int deleteBatchIds(@Param("coll") Collection<?> idList);
int updateById(@Param("et") T entity);
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
default int update(@Param("ew") Wrapper<T> updateWrapper) {
return this.update((Object)null, updateWrapper);
}
T selectById(Serializable id);
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
void selectBatchIds(@Param("coll") Collection<? extends Serializable> idList, ResultHandler<T> resultHandler);
default List<T> selectByMap(Map<String, Object> columnMap) {
return this.selectList((Wrapper)Wrappers.query().allEq(columnMap));
}
default void selectByMap(Map<String, Object> columnMap, ResultHandler<T> resultHandler) {
this.selectList((Wrapper)Wrappers.query().allEq(columnMap), resultHandler);
}
default T selectOne(@Param("ew") Wrapper<T> queryWrapper) {
return (T)this.selectOne(queryWrapper, true);
}
default T selectOne(@Param("ew") Wrapper<T> queryWrapper, boolean throwEx) {
List<T> list = this.selectList(queryWrapper);
int size = list.size();
if (size == 1) {
return (T)list.get(0);
} else if (size > 1) {
if (throwEx) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + size);
} else {
return (T)list.get(0);
}
} else {
return null;
}
}
default boolean exists(Wrapper<T> queryWrapper) {
Long count = this.selectCount(queryWrapper);
return null != count && count > 0L;
}
Long selectCount(@Param("ew") Wrapper<T> queryWrapper);
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
void selectList(@Param("ew") Wrapper<T> queryWrapper, ResultHandler<T> resultHandler);
List<T> selectList(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
void selectList(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper, ResultHandler<T> resultHandler);
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
void selectMaps(@Param("ew") Wrapper<T> queryWrapper, ResultHandler<Map<String, Object>> resultHandler);
List<Map<String, Object>> selectMaps(IPage<? extends Map<String, Object>> page, @Param("ew") Wrapper<T> queryWrapper);
void selectMaps(IPage<? extends Map<String, Object>> page, @Param("ew") Wrapper<T> queryWrapper, ResultHandler<Map<String, Object>> resultHandler);
<E> List<E> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
<E> void selectObjs(@Param("ew") Wrapper<T> queryWrapper, ResultHandler<E> resultHandler);
default <P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper) {
page.setRecords(this.selectList(page, queryWrapper));
return page;
}
default <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param("ew") Wrapper<T> queryWrapper) {
page.setRecords(this.selectMaps(page, queryWrapper));
return page;
}
}
使用UserInfoMapper2继承BaseMapper
java
@Mapper
public interface UserInfoMapper2 extends BaseMapper<UserInfo> {
}
2.4 CRUD单元测试
在创建出来的SpringBoot工程中,在src下的test目录下,已经自动帮我们创建好了测试类 ,我们可以直接使用这个测试类来进行测试. 编写几个单元测试, 测试基本的CRUD功能
java
@SpringBootTest
class UserInfoMapper2Test {
@Autowired
UserInfoMapper2 userInfoMapper2;
@Test
void testInsert() {
UserInfo user = new UserInfo();
user.setUsername("xht");
user.setPassword("123456");
user.setAge(11);
user.setGender(0);
user.setPhone("18610001234");
userInfoMapper2.insert(user);
}
@Test
void testSelectById() {
UserInfo user = userInfoMapper2.selectById(1L);
System.out.println("user: " + user);
}
@Test
void testSelectByIds() {
List<UserInfo> users = userInfoMapper2.selectBatchIds(List.of(1L, 2L, 3L, 4L));
users.forEach(System.out::println);
}
@Test
void testUpdateById() {
UserInfo user = new UserInfo();
user.setId(1);
user.setPassword("4444444");
userInfoMapper2.updateById(user);
}
@Test
void testDelete() {
userInfoMapper2.deleteById(1);
}
}
3. MyBatis-Plus复杂操作
3.1 常见注解
在上面的程序中, MyBatis是如何知道, 我们要操作的是哪张表, 表里有哪些字段呢? 我们来看下咱们Mapper的代码
java
@Mapper
public interface UserInfoMapper2 extends BaseMapper<UserInfo> {
}
UserInfoMapper2 在继承 BaseMapper 时, 指定了一个泛型, 这个UserInfo就是与数据库表相对应的实体类
MyBatis-Plus会根据这个实体类来推断表的信息. 默认情况下:
-
表名: 实体类的驼峰表示法转换成蛇形表示法(下划线分割), 作为表名. 比如UserInfo -> user_info
-
字段:根据实体类的属性名转换为蛇形表示法作为字段名. 比如deleteFlag -> delete_flag
-
主键: 默认为id
那如果实体类和数据库不是按照上述规则定义的呢? MyBatis-Plus也给我们提供了一下注解, 让我们标识表的信息.
3.1.1 @TableName
修改实体类名UserInfo为 Userinfo, 重新执行测试方法testSelectById 运行结果:
java
@Data
public class Userinfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
修改相应方法,运行结果为:

从日志可以看到, 默认查找的表名为userinfo. 我们可以通过 @TableName 来标识实体类对应的表
java
@Data
@TableName("user_info")
public class Userinfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
使用@TableName来标识实体类对应的数据库表名,则可以正常运行;

3.1.2 @TableField
修改属性名 deleteFlag 为 deleteflag, 重新执行测试方法testSelectById 运行结果:
java
@Data
@TableName("user_info")
public class Userinfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteflag;
private Date createTime;
private Date updateTime;
}

从日志可以看到, 根据属性名转换后的字段名为: deleteflag.
我们可以通过 @TableField 来标识 对应的字段名
java
@Data
@TableName("user_info")
public class Userinfo {
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
@TableField("delete_flag")
private Integer deleteflag;
private Date createTime;
private Date updateTime;
}

再次运行程序, 程序运行结果正常.
3.1.3 @TableId
修改属性名 id 为 userId, 重新执行测试方法testSelectById
java
@Data
@TableName("user_info")
public class Userinfo {
private Integer userId;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
@TableField("delete_flag")
private Integer deleteflag;
private Date createTime;
private Date updateTime;
}
运行结果:

我们可以通过 @TableId 来 指定对应的主键
java
@Data
@TableName("user_info")
public class Userinfo {
@TableId("id")
private Integer userId;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
@TableField("delete_flag")
private Integer deleteflag;
private Date createTime;
private Date updateTime;
}
运行结果:

3.2 打印日志
为了方便学习, 我们可以借助日志, 查看Mybatis-Plus执行的SQL语句, 参数和执行结果 Mybatis-Plus配置日志如下:
java
mybatis-plus:
configuration: # 配置打印 MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
在配置文件当中添加了打印日志相关的配置之后,可以看到sql执行的相关信息

3.3 条件构造器
入门程序里的使用, 都是简单的CRUD, 在实际的应用场景中, 我们还需要使用更复杂的操作, MyBatisPlus 也给我们提供了相应的支持.
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper), 用于构建复杂的数据库查询条件. Wrapper 类 允许开发者以链式调用的方式构造查询条件, 无需编写繁琐的 SQL 语句, 从而提高开发效率并减少 SQL 注入的风险.
以下是主要的 Wrapper 类及其功能:
• AbstractWrapper:这是一个抽象基类 , 提供了所有 Wrapper 类共有的方法和属性. 详细参考官网介绍: 条件构造器
• QueryWrapper:用于构造查询条件, 在AbstractWrapper的基础上拓展了一个select方法, 允许指 定查询字段.
• UpdateWrapper: 用于构造更新条件, 可以在更新数据时指定条件.
• LambdaQueryWrapper:基于 Lambda 表达式的查询条件构造器, 它通过 Lambda 表达式来引用 实体类的属性,从而避免了硬编码字段名.
• LambdaUpdateWrapper: 基于 Lambda 表达式的更新条件构造器, 它允许你使用 Lambda 表达 式来指定更新字段和条件,同样避免了硬编码字段名的问题
3.3.1 QueryWrapper
QueryWrapper并不只用于查询语句, 无论是修改, 删除, 查询, 都可以使用QueryWrapper来构建查询条 件.
查询
完成下述SQL查询
sql
SELECT id,username,password,age FROM user_info WHERE age = 18 AND username like "%min%"
在navicat中的运行结果为:

测试代码
java
@Test
void testQueryWrapper(){
QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<UserInfo>()
.select("id","username","password","age")
.eq("age",18)
.like("username","min");
List<UserInfo> userInfos = userInfoMapper2.selectList(userInfoQueryWrapper);
userInfos.forEach(x-> System.out.println(x));
}
查看服务器日志,看到,可以正常运行

注意
QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
QueryWrapper:MyBatis-Plus 提供的条件构造器<UserInfo>:告诉 MyBatis-Plus(MP) 你要查的是哪张表(对应 user_info 表)
更新
完成下述SQL查询
java
UPDATE user_info SET delete_flag=? WHERE age < 20
测试代码
java
@Test
void testUpdateByQueryWrapper(){
QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<UserInfo>()
.lt("age",20);
UserInfo userInfo = new UserInfo();
userInfo.setDeleteFlag(1);
userInfoMapper2.update(userInfo, userInfoQueryWrapper);
}

• lt : "less than" 的缩写,表示小于.
• le : "less than or equal to"的缩写, 表示小于等于
• ge : "greater than or equal to" 的缩写, 表示大于等于.
• gt : "greater than" 的缩写, 表示大于.
• eq : "equals" 的缩写, 表示等于.
• ne : "not equals" 的缩写, 表示不等于
注意:上述语句的执行条件更新
userInfoMapper2.update(userInfo, userInfoQueryWrapper);MP 的 update 方法两个参数:
第一个参数:要更新的值(SET ...)
第二个参数:条件(WHERE ...)
删除
完成下述SQL查询
sql
DELETE FROM user_info WHERE age = 18
测试代码
java
@Test
void testDeleteByQueryWrapper(){
QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<UserInfo>()
.eq("age",11);
userInfoMapper.delete(userInfoQueryWrapper);
}
如果在执行sql语句的过程中会出现很大的id的情况,那我们需要设置数据库id字段自增
java
@Data
public class UserInfo {
@TableId(type = IdType.AUTO) // 关键:type = IdType.AUTO 表示交给数据库自增
private Integer id;
private String username;
private String password;
private Integer age;
private Integer gender;
private String phone;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
我们打算删除这一条记录


发现age=11的记录已经被删除了

3.3.2 UpdateWrapper
对于更新, 我们也可以直接使用 UpdateWrapper, 在不创建实体对象的情况下, 直接设置更新字段和条件.
基础更新: 完成下述SQL查询
sql
UPDATE user_info SET delete_flag=0, age=5 WHERE id IN (1,2,3)
测试代码
java
@Test
void testUpdateByUpdateWrapper(){
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<UserInfo>()
.set("delete_flag",0)
.set("age", 5)
.in("id", List.of(1,2,3));
userInfoMapper2.update(updateWrapper);
}
运行结果为:


基于SQL更新: 完成下述SQL查询
java
UPDATE user_info SET age = age+10 WHERE id IN (1,2,3)
测试代码
java
@Test
void testUpdateBySQLUpdateWrapper(){
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<UserInfo>()
.setSql("age = age+10")
.in("id", List.of(1,2,3));
userInfoMapper2.update(updateWrapper);
}

3.3.3 LambdaQueryWrapper
QueryWrapper 和 UpdateWrapper存在一个问题, 就是需要写死字段名, 如果字段名发生变更, 可能会因为测试不到位酿成事故
MyBatis-Plus 给我们提供了一种基于Lambda表达式的条件构造器, 它通过 Lambda 表达式来引用实体 类的属性,从而避免了硬编码字段名, 也提高了代码的可读性和可维护性.
• LambdaQueryWrapper
• LambdaUpdateWrapper
分别对应上述的QueryWrapper和UpdateWrapper
java
@Test
void testLambdaQueryWrapper(){
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<UserInfo>();
queryWrapper.lambda()
.select(UserInfo::getUsername, UserInfo::getPassword,UserInfo::getAge)
.eq(UserInfo::getId, 1);
userInfoMapper2.selectList(queryWrapper).forEach(System.out::println);
}

切换到 Lambda 模式(核心!)
queryWrapper.lambda()
作用:从普通字符串方式 → 切换成 方法引用方式(编译安全)
好处:
- 不会写错字段名
- IDE 自动提示
- 重构不会出错
注意:
- UserInfo::getUsername 等价于 →数据库字段 username
方法引用 对应的数据库字段 UserInfo::getIdidUserInfo::getUsernameusernameUserInfo::getAgeageUserInfo::getDeleteFlagdelete_flagMP 会自动把驼峰 → 下划线!
- 对于下面语句的解释
javauserInfoMapper2.selectList(queryWrapper).forEach(System.out::println);我们来分成两部分解释这个问题:
a. userInfoMapper2.selectList(queryWrapper)
去数据库查询,返回一组(多个)用户数据 → 得到一个 List 集合
b. .forEach(System.out::println)
遍历刚才查到的列表,把每一个用户对象 打印输出!
拆解:
forEach():循环遍历集合里的每一个元素System.out::println:Java8 方法引用
- 等价于:
每一个元素 -> 打印(元素)即类似于lamda表达式x->System.out.println(x)
3.3.4 LambdaUpdateWrapper
• LambdaUpdateWrapper用法和 LambdaQueryWrapper相似
java
@Test
void testLambdUpdateByUpdateWrapper(){
UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<UserInfo>();
updateWrapper.lambda()
.set(UserInfo::getDeleteFlag, 0)
.set(UserInfo::getAge, 5)
.in(UserInfo::getId, List.of(1,2,3));
userInfoMapper2.update(updateWrapper);
}

查看数据库,结果为:

3.4 自定义SQL
在实际的开发中, MyBatis-Plus提供的操作不能满足我们的实际需求, MyBatis-Plus 也提供了自定义 SQL的功能, 我们可以利用Wrapper构造查询条件, 再结合Mapper编写SQL
代码示例1
完成下述SQL查询
sql
select id,username,password,age FROM user_info WHERE username = "admin"
UserInfoMapper2.java
java
@Mapper
public interface UserInfoMapper2 extends BaseMapper<UserInfo> {
@Select("select id,username,password,age FROM user_info ${ew.customSqlSegment}")
List<UserInfo> queryUserByCustom(@Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
}
UserInfoMapper2Test.java
java
@Test
void queryUserByCustom() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<UserInfo>()
.eq("username","admin");
userInfoMapper2.queryUserByCustom(queryWrapper).forEach(System.out::println);
}

注意事项:
• 参数命名:在自定义 SQL 时, 传递 Wrapper 对象作为参数时, 参数名必须为 ew ,或者使用注解 @Param(Constants.WRAPPER) 明确指定参数为 Wrapper 对象.
• 使用 {ew.customSqlSegment} :在 SQL 语句中,使用 **{ew.customSqlSegment}** 来引用 Wrapper 对象生成的 SQL 片段.
• 不支持基于 entity 的 where 语句:自定义 SQL 时,Wrapper对象不会基于实体类自动生成 where 子句,你需要手动编写完整的 SQL 语句.
代码示例2
MyBatis-Plus 在 MyBatis的基础上只做增强不做改变, 所以也支持XML的实现方式
上述功能也可以使用XML的方式完成
- 配置mapper路径
java
mybatis-plus:
configuration: # 配置打印 MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
mapper-locations: "classpath*:/static/mapper/**.xml" # Mapper.xml
type-aliases-package: com.mxy.mybatisreviewdemo.model
- 定义方法
java
List<UserInfo> queryUserByCustom2(@Param(Constants.WRAPPER) Wrapper<UserInfo> wrapper);
- 编写XML
XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mxy.mybatisreviewdemo.mapper.UserInfoMapper2">
<select id="queryUserByCustom2" resultType="com.mxy.mybatisreviewdemo.model.UserInfo">
select id,username,password,age
FROM user_info ${ew.customSqlSegment}
</select>
</mapper>
- 测试
java
@Test
void queryUserByCustom2() {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<UserInfo>();
queryWrapper.lambda().eq(UserInfo::getAge, 18);
userInfoMapper2.queryUserByCustom2(queryWrapper).forEach(System.out::println);
// LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<UserInfo>()
// .eq(UserInfo::getAge, 18); // 显式写 <UserInfo>,让泛型推断正确
//
// userInfoMapper2.selectList(queryWrapper)
// .forEach(System.out::println);
}

代码示例3
完成下述SQL查询
sql
UPDATE user_info SET age = age+10 WHERE id IN (1,2,3)
Mapper:
java
@Update("UPDATE user_info SET age = age+ #{addAge} ${ew.customSqlSegment}")
void updateUserByCustom(@Param("addAge") int addAge, @Param("ew")
Wrapper<UserInfo> wrapper);
-
#{addAge}:动态参数,安全传值 -
${ew.customSqlSegment}:自动拼接 WHERE 条件 -
@Param("addAge")↔#{addAge} -
@Param("ew")↔${ew.customSqlSegment}
测试代码
java
@Test
void updateUserByCustom(){
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<UserInfo>()
.in("id",List.of(1,2,3));
userInfoMapper.updateUserByCustom(10, queryWrapper);
}
- 10 → 传入
#{addAge} - wrapper → 自动生成
WHERE id IN (1,2,3)
查看服务器日志


注意:
1.下面的两种写法一样
java
// 1. 手写 ew(最常见)
@Param("ew")
// 2. 用常量(最规范、最推荐)
@Param(Constants.WRAPPER)
- 总结一下:
通过注解写自定义 SQL,用 #{} 传普通参数,用 ${ew.customSqlSegment} 自动拼接条件,配合 @Param ("ew") 绑定 Wrapper,就能灵活、安全地实现动态更新。