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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM