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();
posted @ 2020-07-29 20:47  呵呵233  阅读(2673)  评论(0编辑  收藏  举报