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();