MyBatis-Plus 基本用法
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生,官方文档地址:https://mp.baomidou.com/guide/。本文在 SpringBoot 框架的基础上介绍 MyBatis-Plus 的用法。
入门案例
☕️ 数据库脚本
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`user_name` VARCHAR ( 20 ) NOT NULL COMMENT '用户名',
`password` VARCHAR ( 20 ) NOT NULL COMMENT '密码',
`name` VARCHAR ( 30 ) COMMENT '姓名',
`age` INT COMMENT '年龄',
`email` VARCHAR ( 50 ) COMMENT '邮箱'
) COMMENT '测试表';
INSERT INTO `tb_user`(`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES
(1, 'zhangsan', '123456', '张三', 18, 'test1@itcast.cn'),
(2, 'lisi', '123456', '李四', '20', 'test2@itcast.cn'),
(3, 'wangwu', '123456', '王五', '28', 'test3@itcast.cn'),
(4, 'zhaoliu', '123456', '赵六', '21', 'test4@itcast.cn'),
(5, 'sunqi', '123456', '孙七', '24', 'test5@itcast.cn');
☕️ 在 pom.xml 文件中添加 jar 包依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis-Plus-->
<!-- 不需要引入 MyBatis 依赖,MyBatis-Plus 已经集成了 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- junit5 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
☕️ application.properties 配置文件
# 数据库连接配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/learning?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=123456
# MyBatis-Plus 配置
# Mapper 接口所对应的 XML 映射文件位置,默认值是 classpath*:/mapper/**/*.xml
mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml
# 别名包扫描路径,通过该属性可以给包中的类注册别名,默认值为 null
mybatis-plus.type-aliases-package=com.example.entity
# 是否开启驼峰命名规则映射,默认为 true
mybatis-plus.configuration.map-underscore-to-camel-case=true
# 开启二级缓存的全局开关,默认为 true
mybatis-plus.configuration.cache-enabled=true
# 主键生成策略,默认为 assign_id(雪花算法)
mybatis-plus.global-config.db-config.id-type=assign_id
# 全局延迟加载
# 开启全局的延迟加载开关,默认值为 false
mybatis-plus.configuration.lazy-loading-enabled=true
# 设置为 false 表示按需加载,默认值为 true
mybatis-plus.configuration.aggressive-lazy-loading=false
# 打印 sql 语句的日志配置
logging.level.com.example.mapper=trace
☕️ 创建实体类
package com.example.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
// 实体类名与表名匹配不一致,使用 @TableName 指定数据库表名
@TableName("tb_user")
public class User {
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
☕️ 编写 UserMapper 接口,继承 BaseMapper
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
// BaseMapper<T> 是 MyBatis-Plus 提供的通用 Mapper
public interface UserMapper extends BaseMapper<User> {
}
Mapper 接口需要注册到 Spring 容器中,所以在启动类上添加 @MapperScan 注解扫描 mapper 所在包:
@MapperScan("com.example.mapper") // 扫描 mapper 接口所在包
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
☕️ 测试
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
// 使用通用 Mapper 的 selectList() 方法查询所有记录
List<User> userList = userMapper.selectList(null);
Assertions.assertEquals(5, userList.size());
userList.forEach(System.out::println);
}
}
==> Preparing: SELECT id,user_name,password,name,age,email FROM tb_user
==> Parameters:
<== Columns: id, user_name, password, name, age, email
<== Row: 1, zhangsan, 123456, 张三, 18, test1@itcast.cn
<== Row: 2, lisi, 123456, 李四, 20, test2@itcast.cn
<== Row: 3, wangwu, 123456, 王五, 28, test3@itcast.cn
<== Row: 4, zhaoliu, 123456, 赵六, 21, test4@itcast.cn
<== Row: 5, sunqi, 123456, 孙七, 24, test5@itcast.cn
<== Total: 5
User(id=1, userName=zhangsan, password=123456, name=张三, age=18, email=test1@itcast.cn)
User(id=2, userName=lisi, password=123456, name=李四, age=20, email=test2@itcast.cn)
User(id=3, userName=wangwu, password=123456, name=王五, age=28, email=test3@itcast.cn)
User(id=4, userName=zhaoliu, password=123456, name=赵六, age=21, email=test4@itcast.cn)
User(id=5, userName=sunqi, password=123456, name=孙七, age=24, email=test5@itcast.cn)
配置和注解
配置选项
MyBatis-Plus 中有大量配置,其中一部分是 MyBatis 原生的配置,另一部分是 MP 自身的配置,具体配置选项请查看 https://mp.baomidou.com/config/。
基本配置
⭐️ 配置 XML 映射文件位置
# Mapper 接口所对应的 XML 映射文件位置,默认值是 classpath*:/mapper/**/*.xml
mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml
MyBatis-Plus 在 MyBatis 基础上只做增强,不做改变,所以 Mapper 接口仍可自定义方法,在 XML 映射文件中仍可编写方法对应的 SQL 语句。该配置就是告诉 Mapper 接口其对应的 XML 映射文件位置。
在 UserMapper 接口中自定义方法:
// BaseMapper<T> 是 MyBatis-Plus 提供的通用 Mapper
public interface UserMapper extends BaseMapper<User> {
// 根据 id 查询数据
User findById(long id);
}
在 UserMapper.xml 映射文件中编写相应方法的 sql:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="findById" resultType="com.example.entity.User">
select id, user_name, password, name, age, email
from tb_user
where id = #{id}
</select>
</mapper>
对 findById() 方法进行测试:
@Test
public void testFindById() {
User user = userMapper.findById(1L);
System.out.println(user);
}
==> Preparing: select id, user_name, password, name, age, email from tb_user where id = ?
==> Parameters: 1(Long)
<== Columns: id, user_name, password, name, age, email
<== Row: 1, zhangsan, 123456, 张三, 18, test1@itcast.cn
<== Total: 1
User(id=1, userName=zhangsan, password=123456, name=张三, age=18, email=test1@itcast.cn)
⭐️ 配置别名包扫描路径
# 别名包扫描路径,通过该属性可以给包中的类注册别名,默认为 null
mybatis-plus.type-aliases-package=com.example.entity
通过该属性可以批量给指定包下的类设置别名。设置别名后,在 MyBatis 文件的任意位置使用该包下的类时,可以省略包名,不需要配置全限定类名。
<mapper namespace="com.example.mapper.UserMapper">
<!-- resultType 可以直接配置别名,不需要使用全限定的类名 -->
<select id="findById" resultType="User">
select id, user_name, password, name, age, email
from tb_user
where id = #{id}
</select>
</mapper>
进阶配置
✏️ 开启驼峰命名规则映射
# 是否开启驼峰命名规则映射,默认为 true
mybatis-plus.configuration.map-underscore-to-camel-case=true
开启自动驼峰命名规则映射,即将数据库列 a_column 映射到 Java 对象属性 aColumn。
✏️ 开启 MyBatis 的二级缓存
# 开启二级缓存的全局开关,默认为 true
mybatis-plus.configuration.cache-enabled=true
✏️ 开启全局延迟加载
# 开启全局的延迟加载开关,默认值为 false
mybatis-plus.configuration.lazy-loading-enabled=true
# 设置为 false 表示按需加载,默认值为 true
mybatis-plus.configuration.aggressive-lazy-loading=false
DB 策略配置
📚 全局主键生成策略
# 插入记录时的主键生成策略,默认值是 assign_id(雪花算法)
# auto: 数据库主键 ID 自增
# assign_uuid: uuid 随机生成主键 ID
# assign_id:雪花算法生成主键 ID
# input: 用户输入主键 ID
# none: 在全局配置中等于 input
mybatis-plus.global-config.db-config.id-type=assign_id
该策略定义在 IdType 枚举类中,定义如下:
public enum IdType {
AUTO(0), // 数据库主键 ID 自增
NONE(1), // 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT(2), // 用户输入 ID
/* 以下 3 种类型、只有当插入对象 ID 为空,才自动填充。 */
ASSIGN_ID(3), // 使用雪花算法生成主键 ID,主键类型为 number 或 string
ASSIGN_UUID(4), // UUID 随机生成主键 ID,主键类型为 string
}
注意:雪花算法生成 id 的字符串长度为 19,如果想使用 number 类型存储,那么实体类主键属性类型应该为 Long,数据库表主键字段类型应该为 BIGINT(20);UUID 随机生成 id 的字符串长度为 32,所以数据库表主键字段类型应该设置为 VARCHAR(32)。
📚 表名前缀
# 设置表名前缀,全局配置后可省略 @TableName 注解配置,默认值为 null
mybatis-plus.global-config.db-config.table-prefix=tb_
常用注解
MyBatis-Plus 注解使用详情请查看 https://mp.baomidou.com/guide/annotation.html,此处仅介绍一些常用的注解及其属性。
@TableName
该注解前面的入门案例也使用过了,当实体类名与表名不匹配时(默认使用驼峰命名方式),可以使用该注解的 value 属性指定表名:
// 实体类名与表名匹配不一致,使用 @TableName 指定数据库表名
@TableName(value = "tb_user")
public class User {
//...
}
@TableName
注解的其它属性不常用,这里就不多介绍。
@TableId
该注解是用来描述主键的。默认情况下,MyBatis-Plus 会将实体类中名为 id 的属性作为主键标识,如果没有 id 属性,则需要使用 @TableId 注解指定实体类中的主键标识。
@TableId 注解中拥有两个属性:
public @interface TableId {
// 指定数据库表字段名(默认为驼峰命名方式,该值可无)
String value() default "";
// 主键 ID 的生成策略,默认为 NONE,即跟随全局主键生成策略
IdType type() default IdType.NONE;
}
- value:默认的实体类属性名和数据库表字段名映射是驼峰命名规则,如果映射不匹配,则可以使用该属性指定数据库字段名。
- type:默认的主键生成方式是跟随全局主键生成策略,如果不想使用全局策略,则可以使用该属性指定策略,具体策略定义查看前面的 IdType 枚举类。
具体使用如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
// 实体类名与表名匹配不一致,使用 @TableName 指定数据库表名
@TableName(value = "tb_user")
public class User {
@TableId(type = IdType.AUTO) // 主键自增策略
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
@TableField
@TableId 是用来描述主键字段,而 @TableField 是用来描述非主键字段。该注解的属性很多,这里仅介绍一些常用的:
- value:指定数据库表字段名,和前两个注解的 value 属性用法一样。如果实体类属性名和数据库表字段名匹配不一致(默认为驼峰命名规则),则可以使用该属性指定数据库表字段名。
- exist:用于排除实体类中的非表字段。默认值为 true,表示该实体类属性为对应表字段。常用于多表之间的联系,例如一个老师对应多个学生,在 Teacher 类中需要一个 Student 类的集合,就可以将该属性设置为 false,排除 Teacher 类中非表字段
List<Student> students
。
具体使用如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
// 实体类名与表名匹配不一致,使用 @TableName 指定数据库表名
@TableName(value = "tb_user")
public class User {
@TableId(type = IdType.AUTO) // 主键自增策略
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
@TableField(value = "email") // 解决字段名匹配不一致
private String mail;
@TableField(exist = false) // 排除实体类中的非表字段
private String address;
}
@TableField 注解中还拥有 insertStrategy、updateStrategy 和 whereStrategy 三个属性来分别指定字段在 insert 语句、set 语句和 where 条件中所采取的策略。这三个属性指定的字段策略都定义在 FieldStrategy 枚举类中:
public enum FieldStrategy {
IGNORED, // 忽略判断,无论字段是否为 NULL,都会加入 sql 语句中
NOT_NULL, // 非 NULL 判断, 即值为 NULL 的字段不加入 sql 语句
NOT_EMPTY, // 非空判断(只对字符串类型字段,其他类型字段依然为非 NULL 判断)
DEFAULT, // 默认值,在注解中表示跟随全局策略,在全局配置中表示 NOT_NULL
NEVER // 不加入 sql
}
下面介绍这三个属性的具体使用:
✌ 插入策略
/**
* 字段验证策略之 insert: 当插入操作时,该字段拼接 insert 语句时的策略,默认跟随全局策略 NOT_NULL
* IGNORED: 直接拼接 insert into table_a(column) values (#{columnProperty});
* NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>)
* values (<if test="columnProperty != null">#{columnProperty}</if>)
* NOT_EMPTY: insert into table_a (<if test="columnProperty != null and
* columnProperty!=''">column</if>) values (<if test="columnProperty != null
* and columnProperty!=''">#{columnProperty}</if>)
*/
FieldStrategy insertStrategy() default FieldStrategy.DEFAULT;
✌ 更新策略
/**
* 字段验证策略之 update: 当更新操作时,该字段拼接 set 语句时的策略,默认跟随全局策略 NOT_NULL
* IGNORED: 直接拼接 update table_a set column=#{columnProperty},
* 属性为null/空string都会被set进去
* NOT_NULL: update table_a set <if test="columnProperty != null">
* column=#{columnProperty}</if>
* NOT_EMPTY: update table_a set <if test="columnProperty != null and
* columnProperty!=''">column=#{columnProperty}</if>
*/
FieldStrategy updateStrategy() default FieldStrategy.DEFAULT;
✌ 查询策略
/**
* 字段验证策略之 where: 表示该字段在拼接 where 条件时的策略,默认跟随全局策略 NOT_NULL
* IGNORED: 直接拼接 column=#{columnProperty}
* NOT_NULL: <if test="columnProperty != null">column=#{columnProperty}</if>
* NOT_EMPTY: <if test="columnProperty != null and columnProperty!=''">
* column=#{columnProperty}</if>
*/
FieldStrategy whereStrategy() default FieldStrategy.DEFAULT;
由上面可知,在默认配置下使用 MyBatis-Plus提供的 CURD 方法,字段策略都为 NOT_NULL,即值为 null 的字段不加入 sql 语句。
通用 Mapper 方法
自定义 Mapper 通过继承 BaseMapper 就可以获取到各种各样的单表操作,下面我们会介绍这些通用的 CURD。
插入操作
insert() 方法是 BaseMapepr 唯一提供的插入方法:
/**
* 插入一条记录,默认插入策略是 NOT_NULL,即值为 null 的字段不加入 sql 语句
*
* @param entity 实体对象
*/
int insert(T entity);
对 insert() 方法进行测试:
@Test
public void testInsert() {
User user = new User();
user.setUserName("caocao");
user.setPassword("1234567");
user.setName("曹操");
// 返回的 result 是受影响的行数,并不是自增后的 id
int insert = userMapper.insert(user);
Assertions.assertEquals(1, insert);
// 自增的 id 会回填到对象中
System.out.println(user.getId());
}
==> Preparing: INSERT INTO tb_user ( user_name, password, name ) VALUES ( ?, ?, ? )
==> Parameters: caocao(String), 1234567(String), 曹操(String)
<== Updates: 1
6
由上述可以看出,MyBatis-Plus 的默认插入策略是值为 null 的字段不加入 sql 语句中。
更新操作
BaseMapper 提供了以下两个更新方法:
/**
* 根据 ID 更新记录,更新不为 null 的字段
*
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根据 Wrapper 条件,更新不为 null 的字段
*
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
updateById
/**
* 根据 ID 更新记录,更新不为 null 的字段
*
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
对 testUpdateById() 方法进行测试:
@Test
public void testUpdateById() {
User user = new User();
user.setId(6L); // 主键
user.setAge(21); // 更新的字段
// 根据 ID 更新,更新不为 null 的字段
Assertions.assertEquals(1, userMapper.updateById(user));
}
==> Preparing: UPDATE tb_user SET age=? WHERE id=?
==> Parameters: 21(Integer), 6(Long)
<== Updates: 1
update
/**
* 根据 Wrapper 条件,更新不为 null 的字段
*
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
update() 方法是通过条件构造器构造条件,条件构造器可以使用以下两种:
// 设置 where 的查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 设置 where 的查询条件和 set 的更新字段
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
下面使用上述两种条件构造器对 update() 方法进行测试,测试结果是一样的:
@Test
public void testUpdate() {
User user = new User();
user.setAge(22); // 更新的字段
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id", 6); // 查询的条件
Assertions.assertEquals(1, userMapper.update(user, wrapper)); // 更新
}
@Test
public void testUpdate() {
// 查询的条件以及更新的字段
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 6).set("age", 22);
// 更新,由于 warpper 已经设置了更新字段,所以 entity 参数设置为 null
Assertions.assertEquals(1, userMapper.update(null, wrapper));
}
==> Preparing: UPDATE tb_user SET age=? WHERE (id = ?)
==> Parameters: 22(Integer), 6(Integer)
<== Updates: 1
上面的 QueryWrapper 和 UpdateWrapper 是通过自己写表的字段名进行条件构造的,容易发生拼写错误,所以推荐使用 Lambda 条件构造器。Lambda 条件构造器的条件是通过调用实体类中的属性方法来构造,如果方法名称写错会出现错误提示,相对而言更容易纠正。下面使用 Lambda 条件构造器重写上述两个方法:
@Test
public void testUpdate2() {
User user = new User();
user.setAge(23); // 更新的字段
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getId, 6); // 查询的条件
Assertions.assertEquals(1, userMapper.update(user, wrapper)); // 更新
}
@Test
public void testUpdate2() {
// 查询的条件以及更新的字段
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, 6).set(User::getAge, 23);
// 更新,由于 warpper 已经设置了更新字段,所以 entity 参数设置为 null
Assertions.assertEquals(1, userMapper.update(null, wrapper));
}
==> Preparing: UPDATE tb_user SET age=? WHERE (id = ?)
==> Parameters: 23(Integer), 6(Integer)
<== Updates: 1
删除操作
BaseMapper 提供了以下四个通用删除方法:
/**
* 根据 ID 删除记录
*
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 Wrapper 条件,删除记录
*
* @param wrapper 实体对象封装操作类(可以为 null)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
/**
* 删除(根据 ID 批量删除)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
deleteById
/**
* 根据 ID 删除记录
*
* @param id 主键 ID
*/
int deleteById(Serializable id);
@Test
public void testDeleteById() {
// 执行删除操作
Assertions.assertEquals(1, userMapper.deleteById(6L));
}
==> Preparing: DELETE FROM tb_user WHERE id=?
==> Parameters: 6(Long)
<== Updates: 1
deleteByMap
/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
@Test
public void testDeleteByMap() {
// columnMap 存储查询条件,key 存储的是表字段名不是实体类属性名
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("age", 20);
columnMap.put("name", "张三");
// 执行删除操作,预期结果为 0
Assertions.assertEquals(0, userMapper.deleteByMap(columnMap));
}
==> Preparing: DELETE FROM tb_user WHERE name = ? AND age = ?
==> Parameters: 张三(String), 20(Integer)
<== Updates: 0
delete
/**
* 根据 Wrapper 条件,删除记录
*
* @param wrapper 实体对象封装操作类(可以为 null)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
@Test
public void testDelete() {
User user = new User();
user.setAge(20);
user.setName("张三");
// 将实体对象进行包装,会将实体类的非 null 属性作为查询参数
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
// 执行删除操作,预期结果为 0
Assertions.assertEquals(0, userMapper.delete(wrapper));
}
==> Preparing: DELETE FROM tb_user WHERE name = ? AND age = ?
==> Parameters: 张三(String), 20(Integer)
<== Updates: 0
deleteBatchIds
/**
* 删除(根据 ID 批量删除)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
@Test
public void testDeleteBatchIds() {
List<Long> list = Arrays.asList(1L, 10L, 20L);
// 根据 id 集合批量进行删除
Assertions.assertEquals(1, userMapper.deleteBatchIds(list));
}
==> Preparing: DELETE FROM tb_user WHERE id IN ( ? , ? , ? )
==> Parameters: 1(Long), 10(Long), 20(Long)
<== Updates: 1
普通查询操作
MyBatis-Plus 提供了多种单表查询操作,包括根据 id 查询、批量查询、查询单条数据、查询列表等操作。
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
T selectById(Serializable id);
/**
* 查询(根据ID 批量查询)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/**
* 查询(根据 columnMap 条件查询)
*
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 Wrapper 条件,查询一条记录,如果结果超过一条会报错
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值,如果只返回一列时可以使用该方法</p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
selectById
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
T selectById(Serializable id);
@Test
public void testSelectById() {
// 根据 id 查询数据
User user = userMapper.selectById(2L);
System.out.println(user);
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE id=?
==> Parameters: 2(Long)
<== Columns: id, user_name, password, name, age, mail
<== Row: 2, lisi, 123456, 李四, 20, test2@itcast.cn
<== Total: 1
User(id=2, userName=lisi, password=123456, name=李四, age=20, mail=test2@itcast.cn, address=null)
selectBatchIds
/**
* 查询(根据ID 批量查询)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
@Test
public void testSelectBatchIds() {
// 根据 id 集合批量查询
List<User> users = userMapper.selectBatchIds(Arrays.asList(2L, 3L, 10L));
users.forEach(System.out::println);
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE id IN ( ? , ? , ? )
==> Parameters: 2(Long), 3(Long), 10(Long)
<== Columns: id, user_name, password, name, age, mail
<== Row: 2, lisi, 123456, 李四, 20, test2@itcast.cn
<== Row: 3, wangwu, 123456, 王五, 28, test3@itcast.cn
<== Total: 2
User(id=2, userName=lisi, password=123456, name=李四, age=20, mail=test2@itcast.cn, address=null)
User(id=3, userName=wangwu, password=123456, name=王五, age=28, mail=test3@itcast.cn, address=null)
selectByMap
/**
* 查询(根据 columnMap 条件查询)
*
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
@Test
public void testSelectByMap() {
// columnMap 存储查询条件,key 存储的是表字段名不是实体类属性名
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("user_name", "lisi");
columnMap.put("age", 20);
// 根据 Map 进行查询
List<User> users = userMapper.selectByMap(columnMap);
users.forEach(System.out::println);
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE user_name = ? AND age = ?
==> Parameters: lisi(String), 20(Integer)
<== Columns: id, user_name, password, name, age, mail
<== Row: 2, lisi, 123456, 李四, 20, test2@itcast.cn
<== Total: 1
User(id=2, userName=lisi, password=123456, name=李四, age=20, mail=test2@itcast.cn, address=null)
selectOne
/**
* 根据 Wrapper 条件,查询一条记录,如果结果超过一条会报错
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void testSelectOne() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "李四");
// 根据条件查询一条数据,如果结果超过一条会报错
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE (name = ?)
==> Parameters: 李四(String)
<== Columns: id, user_name, password, name, age, mail
<== Row: 2, lisi, 123456, 李四, 20, test2@itcast.cn
<== Total: 1
User(id=2, userName=lisi, password=123456, name=李四, age=20, mail=test2@itcast.cn, address=null)
selectCount
/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void testSelectCount() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 23); // 年龄大于 23 岁
// 根据条件查询数据总数
Assertions.assertEquals(2, userMapper.selectCount(wrapper));
}
==> Preparing: SELECT COUNT( 1 ) FROM tb_user WHERE (age > ?)
==> Parameters: 23(Integer)
<== Columns: COUNT( 1 )
<== Row: 2
<== Total: 1
selectList
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void testSelectList() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 23); // 年龄大于 23 岁
// 根据条件查询查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE (age > ?)
==> Parameters: 23(Integer)
<== Columns: id, user_name, password, name, age, mail
<== Row: 3, wangwu, 123456, 王五, 28, test3@itcast.cn
<== Row: 5, sunqi, 123456, 孙七, 24, test5@itcast.cn
<== Total: 2
User(id=3, userName=wangwu, password=123456, name=王五, age=28, mail=test3@itcast.cn, address=null)
User(id=5, userName=sunqi, password=123456, name=孙七, age=24, mail=test5@itcast.cn, address=null)
selectMaps
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void testSelectMaps() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 23); // 年龄大于 23 岁
// 根据条件查询查询
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE (age > ?)
==> Parameters: 23(Integer)
<== Columns: id, user_name, password, name, age, mail
<== Row: 3, wangwu, 123456, 王五, 28, test3@itcast.cn
<== Row: 5, sunqi, 123456, 孙七, 24, test5@itcast.cn
<== Total: 2
{password=123456, mail=test3@itcast.cn, user_name=wangwu, name=王五, id=3, age=28}
{password=123456, mail=test5@itcast.cn, user_name=sunqi, name=孙七, id=5, age=24}
selectObjs
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值,如果只返回一列时可以使用该方法</p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void testSelectObjs() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getName).gt(User::getAge, 23);
// 根据条件查询查询
List<Object> objs = userMapper.selectObjs(wrapper);
objs.forEach(System.out::println);
}
==> Preparing: SELECT name FROM tb_user WHERE (age > ?)
==> Parameters: 23(Integer)
<== Columns: name
<== Row: 王五
<== Row: 孙七
<== Total: 2
王五
孙七
分页查询操作
MyBatis-Plus 提供了以下两种分页查询方式:
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
配置分页插件
MyBatis-Plus 的分页查询需要配置分页插件:
package com.example.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() { // 配置分页插件
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
selectPage
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void testSelectPage() {
// 年龄大于 20 岁,并按照年龄从小到大排序
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 20).orderByAsc(User::getAge);
// 设置分页条件,第一个参数是当前页(从 1 开始),第二个参数每页最大显示记录数
Page<User> page = new Page<>(2, 2);
// 这里需要注意分页查询返回的对象 userIPage 与传入的对象 page 是同一个对象
IPage<User> userIPage = userMapper.selectPage(page, wrapper);
System.out.println("总页数:" + userIPage.getPages());
System.out.println("总记录数:" + userIPage.getTotal());
System.out.println("每页显示最大记录数:" + userIPage.getSize());
System.out.println("当前页:" + userIPage.getCurrent());
// 返回当前页的记录
userIPage.getRecords().forEach(System.out::println);
}
==> Preparing: SELECT COUNT(1) FROM tb_user WHERE (age > ?)
==> Parameters: 20(Integer)
<== Columns: COUNT(1)
<== Row: 3
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE (age > ?) ORDER BY age ASC LIMIT ?,?
==> Parameters: 20(Integer), 2(Long), 2(Long)
<== Columns: id, user_name, password, name, age, mail
<== Row: 3, wangwu, 123456, 王五, 28, test3@itcast.cn
<== Total: 1
总页数:2
总记录数:3
每页显示最大记录数:2
当前页:2
User(id=3, userName=wangwu, password=123456, name=王五, age=28, mail=test3@itcast.cn, address=null)
selectMapsPage
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
@Test
public void testSelectMapsPage() {
// 年龄大于 20 岁,并按照年龄从小到大排序
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 20).orderByAsc(User::getAge);
// 设置分页条件,第一个参数是当前页(从 1 开始),第二个参数每页最大显示记录数
Page<Map<String, Object>> page = new Page<>(2, 2);
// 这里需要注意分页查询返回的对象 userIPage 与传入的对象 page 是同一个对象
IPage<Map<String, Object>> userIPage = userMapper.selectMapsPage(page, wrapper);
System.out.println("总页数:" + userIPage.getPages());
System.out.println("总记录数:" + userIPage.getTotal());
System.out.println("每页显示最大记录数:" + userIPage.getSize());
System.out.println("当前页:" + userIPage.getCurrent());
// 返回当前页的记录
userIPage.getRecords().forEach(System.out::println);
}
==> Preparing: SELECT COUNT(1) FROM tb_user WHERE (age > ?)
==> Parameters: 20(Integer)
<== Columns: COUNT(1)
<== Row: 3
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE (age > ?) ORDER BY age ASC LIMIT ?,?
==> Parameters: 20(Integer), 2(Long), 2(Long)
<== Columns: id, user_name, password, name, age, mail
<== Row: 3, wangwu, 123456, 王五, 28, test3@itcast.cn
<== Total: 1
总页数:2
总记录数:3
每页显示最大记录数:2
当前页:2
{password=123456, mail=test3@itcast.cn, user_name=wangwu, name=王五, id=3, age=28}
自定义分页方法
在 UserMapper 接口定义分页方法:
public interface UserMapper extends BaseMapper<User> {
// 第一个参数必须为 Page 对象,MyBatis-Plus 会自动对查询语句加上分页处理
// 注意:分页返回的对象与传入的对象是同一个
IPage<User> selectUserPage(Page<User> page, Integer age);
}
在 UserMapper.xml 文件中编写对应的 sql 语句:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- MyBatis-Plus 会自动对查询语句加上分页处理,所以 SQL 语句中不用添加 limit 语句 -->
<select id="selectUserPage" resultType="com.example.entity.User">
select id,user_name,password,name,age,email AS mail
from tb_user
where age > #{age} order by age
</select>
</mapper>
对 selectUserPage() 方法进行测试:
@Test
public void testSelectUserPage() {
// 设置分页条件,第一个参数是当前页(从 1 开始),第二个参数每页最大显示记录数
Page<User> page = new Page<>(2, 2);
// 这里需要注意分页查询返回的对象 userIPage 与传入的对象 page 是同一个对象
IPage<User> userIPage = userMapper.selectUserPage(page, 20);
System.out.println("总页数:" + userIPage.getPages());
System.out.println("总记录数:" + userIPage.getTotal());
System.out.println("每页显示最大记录数:" + userIPage.getSize());
System.out.println("当前页:" + userIPage.getCurrent());
// 返回当前页的记录
userIPage.getRecords().forEach(System.out::println);
}
==> Preparing: SELECT COUNT(1) FROM tb_user WHERE (age > ?)
==> Parameters: 20(Integer)
<== Columns: COUNT(1)
<== Row: 3
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE (age > ?) ORDER BY age ASC LIMIT ?,?
==> Parameters: 20(Integer), 2(Long), 2(Long)
<== Columns: id, user_name, password, name, age, mail
<== Row: 3, wangwu, 123456, 王五, 28, test3@itcast.cn
<== Total: 1
总页数:2
总记录数:3
每页显示最大记录数:2
当前页:2
User(id=3, userName=wangwu, password=123456, name=王五, age=28, mail=test3@itcast.cn, address=null)
通用 Service 方法
MyBatis-Plus 不但提供了通用 Mapper 方法,还提供了通用 Service 方法。通用 Service 的方法可以查看官方文档:https://mp.baomidou.com/guide/crud-interface.html#service-crud-接口,查询、更新、插入和删除的用法和通用 Mapper 类似,这里只是简单介绍。
入门案例
✍ 创建一个 Service 层接口
package com.example.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.entity.User;
// IService<T> 是 MyBatis-Plus 提供的通用 Service 接口,定义了常用的对 T 数据表的操作
public interface UserService extends IService<User> {
}
✍ 实现 Service 接口
package com.example.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import org.springframework.stereotype.Service;
// ServiceImpl<M extends BaseMapper<T>, T> 接口是对 IService<T> 接口的实现
// 第一个泛型 M 指定继承了 BaseMapper 接口的子接口
// 第二个泛型 T 指定实体类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
✍ 测试
@SpringBootTest
public class UserServiceImplTest {
@Autowired
private UserService userService;
@Test
public void testSaveOrUpdate() {
User user = new User();
user.setId(10000L); // 设置一个不存在的主键值
user.setUserName("wangba");
user.setPassword("123456");
user.setName("王八");
user.setAge(29);
user.setMail("update@email");
// saveOrUpdate():该方法首先会根据实体类主键字段查询相关记录,如果记录存在
// 则执行更新操作,如果记录不存在则执行插入操作
Assertions.assertEquals(true, userService.saveOrUpdate(user));
// 插入操作后,主键会回填
System.out.println(user.getId());
}
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE id=?
==> Parameters: 10000(Long)
<== Total: 0
==> Preparing: INSERT INTO tb_user ( user_name, password, name, age, email ) VALUES ( ?, ?, ?, ?, ? )
==> Parameters: wangba(String), 123456(String), 王八(String), 29(Integer), update@email(String)
<== Updates: 1
7
CURD 接口
Save
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量),batchSize 是插入批次数量
boolean saveBatch(Collection<T> entityList, int batchSize);
SaveOrUpdate
// 首先根据主键查询相关记录,如果记录存在则执行更新操作,如果不存在则执行插入操作
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入,batchSize 是插入批次数量
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
Remove
// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
Update
// 根据 UpdateWrapper 条件,更新记录,需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereEntity 条件,更新记录
boolean update(T entity, Wrapper<T> updateWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新,batchSize 是更新批次数量
boolean updateBatchById(Collection<T> entityList, int batchSize);
Get
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
List
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
Page
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
Count
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
条件构造器
前面的通用 Mapper 方法中有使用到条件构造器,此部分会详细介绍条件构造器的条件函数及其使用。更为详细的介绍查看官方文档 https://mp.baomidou.com/guide/wrapper.html。
条件函数
allEq
// params: key为数据库字段名,value为字段值
// null2IsNull: 为 true 则在 value 值为 null 时会在 sql 中调用 isNull 方法,默认为 true
// 为 false 则忽略为 null 的 value 值
// condition:布尔值,表示该条件是否加入最后生成的 sql 中,默认为 true
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
// 例子1:
allEq({id:1,name:"老王",age:null})--->id = 1 and name = '老王' and age is null
// 例子2:
allEq({id:1,name:"老王",age:null}, false)--->id = 1 and name = '老王'
// 比起前面多增加了一个 fiter 参数,该参数是一个过滤器,过滤掉不符合要求的 key-value 对
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
// 例子1:
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})--->name = '老王' and age is null
// 例子 2:
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)--->name = '老王'
@Test
public void testAllEq() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Map<String, Object> params = new HashMap<>();
params.put("name", "李四");
params.put("age", null);
// allEq(condition, filter, params, null2IsNull)
// condition 为 true,该条件才会加入最后生成的 sql 中
// filter 使用 lambda 表达式过滤 name 属性不能加入 sql 语句
// null2IsNull 为 true,为 null 的 value 值会调用 isNull 方法判断
wrapper.allEq(params.get("name") != null, (k, v) -> ! k.equals("name"), params, true);
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE (age IS NULL)
==> Parameters:
<== Total: 0
由上可以看出,当 null2IsNull 为 true,不能忽略为 null 的 value 值,而是调用 isNull 方法判断。
eq 和 ne
// 等于 "="
ne(R column, Object val)
ne(boolean condition, R column, Object val)
// 例子
eq("name", "老王")--->name = '老王'
// 不等于 "<>"("!=")
ne(R column, Object val)
ne(boolean condition, R column, Object val)
// 例子
ne("name", "老王")--->name != '老王'
gt、ge、lt 和 le
// 大于 ">"
gt(R column, Object val)
gt(boolean condition, R column, Object val)
// 例子
gt("age", 18)--->age > 18
// 大于等于 ">="
ge(R column, Object val)
ge(boolean condition, R column, Object val)
// 例子
ge("age", 18)--->age >= 18
// 小于 "<"
lt(R column, Object val)
lt(boolean condition, R column, Object val)
// 例子
lt("age", 18)--->age < 18
// 小于等于 "<="
le(R column, Object val)
le(boolean condition, R column, Object val)
// 例子
le("age", 18)--->age <= 18
between 和 notBetween
// BETWEEN 值1 AND 值2
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
// 例子
notBetween("age", 18, 30)--->age not between 18 and 30
// NOT BETWEEN 值1 AND 值2
notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
// 例子
notBetween("age", 18, 30)--->age not between 18 and 30
Like、notLike、likeLeft 和 likeRight
// LIKE '%值%'
like(R column, Object val)
like(boolean condition, R column, Object val)
// 例子
like("name", "王")--->name like '%王%'
// NOT LIKE '%值%'
notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
// 例子
notLike("name", "王")--->name not like '%王%'
// LIKE '%值'
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
// 例子
likeLeft("name", "王")--->name like '%王'
// LIKE '值%'
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
// 例子
likeRight("name", "王")--->name like '王%'
isNull 和 isNotNull
// 字段 IS NULL
isNull(R column)
isNull(boolean condition, R column)
// 例子
isNull("name")--->name is null
// 字段 IS NOT NULL
isNotNull(R column)
isNotNull(boolean condition, R column)
// 例子
isNotNull("name")--->name is not null
in、notIn、inSql 和 notInSql
// 字段 IN (value.get(0), value.get(1), ...)
in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
// 例子
in("age",{1,2,3})--->age in (1,2,3)
// 字段 IN (v0, v1, ...)
in(R column, Object... values)
in(boolean condition, R column, Object... values)
// 例子
in("age", 1, 2, 3)--->age in (1,2,3)
// 字段 NOT IN (value.get(0), value.get(1), ...)
notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)
// 例子
notIn("age",{1,2,3})--->age not in (1,2,3)
// 字段 NOT IN (v0, v1, ...)
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
// 例子
notIn("age", 1, 2, 3)--->age not in (1,2,3)
// 字段 IN(sql语句)
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
// 例子1
inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6)
// 例子2
inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)
// 字段 NOT IN ( sql语句 )
notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)
// 例子1
notInSql("age", "1,2,3,4,5,6")--->age not in (1,2,3,4,5,6)
// 例子2
notInSql("id", "select id from table where id < 3")--->id not in (select id from table where id < 3)
groupBy 和 having
// 分组:GROUP BY 字段, ...
groupBy(R... columns)
groupBy(boolean condition, R... columns)
// 例子
groupBy("id", "name")--->group by id,name
// HAVING ( sql语句 )
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
// 例子1
having("sum(age) > 10")--->having sum(age) > 10
// 例子2
having("sum(age) > {0}", 11)--->having sum(age) > 11
orderBy、orderByAsc 和 orderByDesc
// 排序:ORDER BY 字段, ...
orderBy(boolean condition, boolean isAsc, R... columns)
// 例子
orderBy(true, true, "id", "name")--->order by id ASC,name ASC
// 排序:ORDER BY 字段, ... ASC
orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
// 例子
orderByAsc("id", "name")--->order by id ASC,name ASC
// 排序:ORDER BY 字段, ... DESC
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
// 例子
orderByDesc("id", "name")--->order by id DESC,name DESC
or、and 和 nested
// 拼接 OR 关键字
or()
or(boolean condition)
// 例子
// 主动调用 or 表示紧接着下一个方法不是用 and 连接!(不调用 or 则默认为使用 and 连接)
eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王'
// OR 嵌套
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
// 例子
or(i -> i.eq("name", "李白").ne("status", "活着"))--->or (name = '李白' and status <> '活着')
// AND 嵌套
and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
// 例子
and(i -> i.eq("name", "李白").ne("status", "活着"))--->and (name = '李白' and status <> '活着')
// 正常嵌套,不带 AND 或者 OR
nested(Consumer<Param> consumer)
nested(boolean condition, Consumer<Param> consumer)
// 例子
nested(i -> i.eq("name", "李白").ne("status", "活着"))--->(name = '李白' and status <> '活着')
apply 和 last
// 拼接 sql
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
// 例子1
apply("id = 1")--->id = 1
// 例子2
apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08')
// 例子3,例子2中的拼接方式存在 sql 注入的风险,所以可以动态入参,使用 {index}
apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
// 无视优化规则直接拼接到 sql 的最后
// 只能调用一次,多次调用以最后一次为准,有 sql 注入的风险,请谨慎使用
last(String lastSql)
last(boolean condition, String lastSql)
// 例子
last("limit 1")
exists 和 notExists
// 拼接 EXISTS (sql语句)
exists(String existsSql)
exists(boolean condition, String existsSql)
// 例子
exists("select id from table where age = 1")--->exists (select id from table where age = 1)
// 拼接 NOT EXISTS (sql语句)
notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
// 例子
notExists("select id from table where age = 1")--->not exists (select id from table where age = 1)
select
// QueryWrapper 特有的条件函数,设置 SELECT 字段(查询返回的字段)
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
// 例子1
select("id", "name", "age")
// 例子2,使用 Lambda 表达式过滤查询字段(主键除外)
select(i -> i.getProperty().startsWith("test"))
set、setSql
// UpdateWrapper 特有的条件函数,设置 SET 字段(更新字段)
set(String column, Object val)
set(boolean condition, String column, Object val)
// 例子1
set("name", "老李头")
// 例子2
set("name", "")--->数据库字段值变为空字符串
// 例子3
set("name", null)--->数据库字段值变为null
// UpdateWrapper 特有的条件函数,设置 SET 部分 SQL(更新字段)
setSql(String sql)
// 例子
setSql("name = '老李头'")
QueryWarpper 构造器
QueryWarpper 构造器可用于构造查询条件和设置查询返回的字段,其有三个构造方法:
public QueryWrapper() {
this(null);
}
// 将实体类传入 Wrapper,会将实体类的非 null 属性作为查询参数
public QueryWrapper(T entity) {
super.setEntity(entity);
super.initNeed();
}
// 将实体类传入 Wrapper,并设置返回的查询字段
public QueryWrapper(T entity, String... columns) {
super.setEntity(entity);
super.initNeed();
this.select(columns);
}
下面会演示这三个构造方法的使用:
💡 使用QueryWrapper()
构造方法
@Test
public void testQueryWrapper() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 查询 user_name 以 w 开头,年龄大于 25 的用户
wrapper.likeRight("user_name", "w").gt("age", 25);
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
==> Preparing: SELECT id,user_name,password,name,age,email AS mail FROM tb_user WHERE ==> Parameters: w%(String), 25(Integer)
<== Columns: id, user_name, password, name, age, mail
<== Row: 3, wangwu, 123456, 王五, 28, test3@itcast.cn
<== Total: 1
User(id=3, userName=wangwu, password=123456, name=王五, age=28, mail=test3@itcast.cn, address=null)
💡 使用QueryWrapper(T entity)
构造方法
@Test
public void testQueryWrapper2() {
User user = new User();
user.setAge(28);
user.setMail("test3@itcast.cn");
// 将实体类传入 Wrapper,会将实体类的非 null 属性作为查询参数
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
wrapper.select("name"); // 设置查询的返回字段为 name
List<Object> objectList = userMapper.selectObjs(wrapper);
objectList.forEach(System.out::println);
}
==> Preparing: SELECT name FROM tb_user WHERE age=? AND email=?
==> Parameters: 28(Integer), test3@itcast.cn(String)
<== Columns: name
<== Row: 王五
<== Total: 1
王五
💡 使用QueryWrapper(T entity, String... columns)
构造方法
@Test
public void testQueryWrapper3() {
User user = new User();
user.setAge(28);
user.setMail("test3@itcast.cn");
// 将实体类传入 Wrapper,并设置查询的返回字段
QueryWrapper<User> wrapper = new QueryWrapper<>(user, "id", "name");
List<Map<String, Object>> mapList = userMapper.selectMaps(wrapper);
mapList.forEach(System.out::println);
}
==> Preparing: SELECT id,name FROM tb_user WHERE age=? AND email=?
==> Parameters: 28(Integer), test3@itcast.cn(String)
<== Columns: id, name
<== Row: 3, 王五
<== Total: 1
{name=王五, id=3}
UpdateWarpper 构造器
UpdateWarpper 构造器可用于构造查询条件和设置更新字段,其有三个构造器:
public UpdateWrapper() {
// 如果无参构造函数,请注意实体 NULL 情况 SET 必须有否则 SQL 异常
this(null);
}
public UpdateWrapper(T entity) {
super.setEntity(entity);
super.initNeed();
this.sqlSet = new ArrayList<>();
}
private UpdateWrapper(T entity, List<String> sqlSet, AtomicInteger paramNameSeq,
Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments,
SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) {
super.setEntity(entity);
this.sqlSet = sqlSet;
this.paramNameSeq = paramNameSeq;
this.paramNameValuePairs = paramNameValuePairs;
this.expression = mergeSegments;
this.lastSql = lastSql;
this.sqlComment = sqlComment;
this.sqlFirst = sqlFirst;
}
使用方式和 QueryWarpper 构造器差不多。
Lamdba 构造器
前面也说过,QueryWrapper 和 UpdateWrapper 是通过自己写表的字段名进行条件构造的,容易发生拼写错误,所以推荐使用 Lambda 条件构造器。Lambda 条件构造器的条件是通过调用实体类中的属性方法来构造,如果方法名称写错会出现错误提示,相对而言更容易纠正。
MyBatis-Plus 提供以下三种种方式构造 LambdaQueryWrapper 和 LambdaUpdateWrapper 条件构造器:
LambdaQueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<User>().lambda();
LambdaQueryWrapper<User> lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
LambdaQueryWrapper<User> lambdaQueryWrapper2 = Wrappers.lambdaQuery();
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new UpdateWrapper<User>().lambda();
LambdaUpdateWrapper<User> lambdaUpdateWrapper1 = new LambdaUpdateWrapper<>();
LambdaUpdateWrapper<User> lambdaUpdateWrapper2 = Wrappers.lambdaUpdate();