SpringBoot2.x 集成 Mybatis-Plus

本文主要对SpringBoot2.x集成Mybatis-Plus及其基本使用进行简单总结,其中SpringBoot使用的2.4.5版本。

一、Mybatis-Plus简介

MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

二、集成Mybatis-Plus

1.创建数据库及表

-- 创建测试数据库并使用
CREATE DATABASE mybatis_plus_test CHARACTER SET utf8 COLLATE utf8_general_ci;
USE mybatis_plus_test;

-- 创建测试表
CREATE TABLE user
(
	id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
	username VARCHAR(30) NOT NULL COMMENT '用户名', 
    password VARCHAR(30) NOT NULL COMMENT '密码',
	realname VARCHAR(30) NULL DEFAULT NULL COMMENT '用户真实姓名',
    gender CHAR(1) NULL DEFAULT NULL COMMENT '性别', 
	age INT(11) NULL DEFAULT NULL COMMENT '年龄',
	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    user_point INT NOT NULL DEFAULT 0 COMMENT '用户积分', 
    user_level TINYINT NOT NULL DEFAULT 1 COMMENT '用户LV:1,2,3,4,5,6',
    birthday TIMESTAMP NULL DEFAULT NULL COMMENT '出生日期',
	PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 comment '用户表';

-- 插入测试数据
INSERT INTO user (id, username, password, realname, gender, age, email, user_point, user_level, birthday) VALUES
(1, 'guanyuchang123', 'a123456', '关羽', '男', 28, 'yunchang@xxx.com', 100, 1, '1992-10-01 17:15:20'),
(2, 'qiaolaoda888', 'ss0635gh', '大乔', '女', 20, 'daqiao@xxx.com', 360, 1, '2000-12-25 13:22:32'),
(3, 'feige45', 'qwer1234aa', '张飞', '男', 25, 'yide@xxx.com', 1000, 3, '1995-05-16 08:10:15'),
(4, 'zilongzhao01', 'qscrdx265', '赵云', '男', 21, 'zilong@xxx.com', 666, 2, '1999-11-27 22:15:25'),
(5, 'qiaoxiaomei886', '123wwqqs36', '小乔', '女', 18, 'xiaoqiao@xxx.com', 2500, 4, '2002-09-06 12:28:33'),
(6, 'shangxiang23', 'asrf0325ss', '孙尚香', '女', 22, 'shangxiang@xxx.com', 10000, 6, '1998-03-11 21:51:10'),
(7, 'liuxuande66', 'zxcv456es', '刘备', '男', 35, 'xuande@xxx.com', 5000, 5, '1985-12-25 13:22:32'),
(8, 'diaochan321', 'asoplk66', '貂蝉', '女', 22, 'diaochan@xxx.com', 888, 3, '1998-06-19 07:22:36'),
(9, 'xiahou360', 'a1s2d3q6', '夏侯惇', '男', 30, 'xiahoudun@xxx.com', 1200, 3, '1990-08-16 23:17:51'),
(10, 'jiangdongyige09', '637cvxs', '孙策', '男', 25, 'sunce@xxx.com', 3000, 4, '1995-12-06 11:16:45');

1
navicat查看用户表:
2

2.初始化工程

通过Maven新建一个名为springboot-mybatis-plus的项目,该项目将连接mysql数据库进行测试。

3.引入依赖

<!-- mybatis-plus 起步依赖 包含了mybatis的所有依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
<!-- druid连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.24</version>
</dependency>
<!-- lombok插件 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
</dependency>

3.主启动类

package com.rtxtitanv;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.MybatisPlusApplication
 * @description 主启动类
 * @date 2021/5/16 15:37
 */
@SpringBootApplication
@MapperScan("com.rtxtitanv.mapper")
public class MybatisPlusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class, args);
    }
}

@MapperScan:扫描mapper接口所在包。

4.编写配置文件

application.yml中进行如下配置:

spring:
  # 配置数据源
  datasource:
    # 数据源类型,使用druid连接池
    type: com.alibaba.druid.pool.DruidDataSource
    # 数据库驱动,这里配置高版本mysql驱动
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mybatis_plus_test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
    username: root
    password: root

5.创建实体类

package com.rtxtitanv.model;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.time.LocalDateTime;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.model.User
 * @description 用户实体类
 * @date 2021/5/16 15:42
 */
@Accessors(chain = true) // lombok注解,支持链式编程
@AllArgsConstructor // lombok注解,自动生成全参构造器
@NoArgsConstructor // lombok注解,自动生成无参构造器
@Data // lombok注解,自动生成get、set、toString等方法
@TableName(value = "user") // 表名注解,建立实体类与数据库表的映射关系
public class User {
    /**
     * 用户ID
     */
    @TableId(value = "id", type = IdType.AUTO) // 主键注解,建立实体类中实例域与表中主键字段的映射关系
    private Long id;
    /**
     * 用户名
     */
    @TableField(value = "username") // 字段注解(非主键),建立实体类中实例域与表中(非主键)字段的映射关系
    private String username;
    /**
     * 用户密码
     */
    @TableField(value = "password")
    private String password;
    /**
     * 用户真实姓名
     */
    @TableField(value = "realname")
    private String realname;
    /**
     * 性别
     */
    @TableField(value = "gender")
    private String gender;
    /**
     * 年龄
     */
    @TableField(value = "age")
    private Integer age;
    /**
     * 邮箱
     */
    @TableField(value = "email")
    private String email;
    /**
     * 用户积分
     */
    @TableField(value = "user_point")
    private Integer userPoint;
    /**
     * 用户LV
     */
    @TableField(value = "user_level")
    private Byte userLevel;
    /**
     * 出生日期
     */
    @TableField(value = "birthday")
    private LocalDateTime birthday;
}

@TableName:表名注解,建立实体类与数据库表的映射关系。

  • 常用属性:
    • value:String类型,指定映射的表名,默认值为""
    • resultMap:String类型,指定xml配置中resultMap的id值,默认值为""
    • autoResultMap:boolean类型,指定是否自动构建resultMap并使用(如果设置resultMap则不会进行resultMap的自动构建并注入),默认值为false
    • excludeProperty:String[]类型,指定需要排除的属性名(@since 3.3.1),默认值为{}

@TableField:主键注解,建立实体类中实例域与表中主键字段的映射关系。

  • 常用属性:
    • value:String类型,指定主键字段名,默认值为""
    • type:Enum类型,指定主键生成类型,默认值为IdType.NONE

@TableField:字段注解(非主键),建立实体类中实例域与表中(非主键)字段的映射关系。

  • 常用属性:
    • value:String类型,指定数据库字段名,默认值为""
    • exist:boolean类型,指定是否为数据库表字段,默认值为true
    • fill:Enum类型,指定字段自动填充策略,默认值为FieldFill.DEFAULT

6.创建Mapper接口

package com.rtxtitanv.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.rtxtitanv.model.User;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.mapper.UserMapper
 * @description UserMapper
 * @date 2021/5/16 15:43
 */
public interface UserMapper extends BaseMapper<User> {
}

7.创建测试类

创建测试类,使用UserMapper查询用户列表:

package com.rtxtitanv;

import com.rtxtitanv.mapper.UserMapper;
import com.rtxtitanv.model.User;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.*;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.MybatisPlusTest
 * @description Mybatis-Plus单元测试类
 * @date 2021/5/16 15:44
 */
@SpringBootTest
class MybatisPlusTest {

    @Resource
    private UserMapper userMapper;
    private static Logger logger = LoggerFactory.getLogger(MybatisPlusTest.class);

    /**
     * 查询所有记录测试
     */
    @Test
    void selectAllTest() {
        List<User> users = userMapper.selectList(null);
        if (users.isEmpty()) {
            logger.info("不存在用户数据");
        } else {
            users.forEach(user -> logger.info(user.toString()));
        }
    }
}

UserMapper中的selectList()方法的参数为MP内置的条件封装器Wrapper,所以不填写就是无任何条件。

运行测试方法selectAllTest()查询所有记录:
3

三、常用配置

MyBatis-Plus中有大量的配置,其中一部分为MyBatis原生所支持的配置,另一部分为MyBatis-Plus的配置。详情可参考MyBatis-Plus配置官方文档

1.configLocation

MyBatis配置文件位置,如果有单独的MyBatis配置,需将其路径配置到configLocation中。下面在resources下创建一个目录mybatis,在mybatis目录下创建一个mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>

mybatis-config.xml中暂时还没有任何配置。然后在application.yml中新增以下配置:

# MyBatis-Plus配置
mybatis-plus:
  # MyBatis配置文件位置
  config-location: classpath:mybatis/mybatis-config.xml

运行测试方法selectAllTest()查询所有记录:
4
没有发现问题,如果将MyBatis配置文件位置写错,测试方法在执行时会报错:
5

2.mapperLocations

MyBatis Mapper所对应的XML文件位置,如果在Mapper中有自定义方法(XML中有自定义实现),需要进行该配置,告诉Mapper所对应的XML文件位置。如果不配置mapperLocations时,Mapper对应的XML文件存放路径和文件名需要和Mapper保持一致。

首先在resources/com/rtxtitanv/mapper下新建UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rtxtitanv.mapper.UserMapper">

    <select id="findById" resultType="com.rtxtitanv.model.User" parameterType="java.lang.Long">
        select * from user where id = #{id}
    </select>

</mapper>

UserMapper接口中新增findById方法:

User findById(Long id);

在测试类中新增以下测试方法:

/**
 * 根据ID查询测试,通过Mapper中自定义方法查询
 */
@Test
void findById() {
    User user = userMapper.findById(3L);
    logger.info(user.toString());
}

运行测试方法findById()
6
下面将UserMapper.xml放到resources/mybatis/mapper下,再次运行测试方法findById()会报错:
7
application.yml中新增以下配置:

mybatis-plus:
  # MyBatis Mapper所对应的XML文件位置
  mapper-locations: classpath*:mybatis/mapper/*.xml

Maven多模块项目的扫描路径需以classpath*:开头(即加载多个jar包下的XML文件)。

再次运行测试方法findById()
8

3.typeAliasesPackage

MyBaits别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在Mapper对应的XML文件中可以直接使用类名,而不用使用全限定的类名(即XML中调用的时候不用包含包名)。

下面将UserMapper.xml中id为findById的select元素的resultType属性改为user:

<select id="findById" resultType="user" parameterType="java.lang.Long">
    select * from user where id = #{id}
</select>

然后运行测试方法findById肯定会报错,因为没有配置别名:
9
application.yml中新增以下配置:

mybatis-plus:
  # MyBaits别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.rtxtitanv.model

再次运行测试方法findById()
10

4.mapUnderscoreToCamelCase

是否开启自动驼峰命名规则(camel case)映射,在MyBatis-Plus中默认为开启。

此属性在MyBatis中原默认值为false,在MyBatis-Plus中,此属性也将用于生成最终的SQL的select body。如果数据库命名符合规则可以不使用@TableField注解指定数据库字段名。

首先将User类中userPointuserLevel上的@TableField注解去掉,然后运行测试方法findById()
11
发现表中user_pointuser_level字段值仍然封装进了User对象中,说明在MyBatis-Plus中自动驼峰命名规则映射默认开启。

application.yml中新增以下配置:

mybatis-plus:
  # 大都为MyBatis原生支持的配置,可以通过MyBatis XML配置文件的形式进行配置
  configuration:
    # 是否开启自动驼峰命名规则映射,MyBatis-Plus中默认值为true,MyBatis中原默认值为false
    map-underscore-to-camel-case: false

再次运行测试方法findById(),报了一个错:
12
错误原因为configLocationconfiguration不能同时配置。注释掉configLocation配置后,再次运行测试方法findById可见自动驼峰命名规则映射已经关闭:
13
注释掉configuration配置,放开configLocation配置,在mybatis-config.xml中也可以配置自动驼峰命名规则。在mybatis-config.xml中的configuration标签下新增以下配置:

<settings>
    <!--  是否开启自动驼峰命名规则映射 -->
    <setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>

再次运行测试方法findById()可见自动驼峰命名规则映射已经关闭:
14

5.logImpl

指定MyBatis所用日志的具体实现。通过配置mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl可以将sql语句以及结果输出到日志。

application.yml中新增以下配置:

mybatis-plus:
  configuration:
    # 将sql及结果输出到日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

运行测试方法findById(),发现sql语句和结果输出到了日志:
15
通过配置logging.level.com.rtxtitanv.mapper=debug也可以输出sql日志,com.rtxtitanv.mapper为mapper接口所在包路径。

application.yml中新增以下配置:

# 输出sql日志,com.rtxtitanv.mapper为mapper接口所在包路径
logging:
  level:
    com:
      rtxtitanv:
        mapper: debug

运行测试方法findById()(测试前先将log-impl注释掉),发现sql语句输出到了日志:
16

四、基本的增删改查

BaseMapper封装了基本的CRUD方法,BaseMapper源码如下:

public interface BaseMapper<T> extends Mapper<T> {

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);

    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,删除记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 删除(根据ID 批量删除)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * 根据 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);

    /**
     * 根据 entity 条件,查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @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);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @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);
}

1.增加方法

插入一条记录:

/**
 * 插入一条记录测试
 * 使用方法 int insert(T entity)
 */
@Test
void insertTest() {
    User user = new User();
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime localDateTime = LocalDateTime.parse("1993-10-16 20:36:10", dateTimeFormatter);
    user.setUsername("kong1ming").setPassword("mkg568er7k").setRealname("诸葛亮").setGender("男").setAge(27)
        .setEmail("kongming@xxx.com").setUserPoint(6000).setUserLevel(Byte.valueOf("5")).setBirthday(localDateTime);
    // 返回的result是受影响的行数,并不是自增后的ID
    int result = userMapper.insert(user);
    logger.info("result: " + result);
    // 自增后的ID会回填到对象中
    logger.info("新增加的一条记录的ID:" + user.getId());
}

控制台打印的日志:
17
user表中成功插入一条记录:
18

2.删除方法

根据ID删除:

/**
 * 根据ID删除测试
 * 使用方法 int deleteById(Serializable id)
 */
@Test
void deleteByIdTest() {
    int result = userMapper.deleteById(11L);
    logger.info("result: " + result);
}

控制台打印的日志:
19
user表id为11的记录已成功删除:
20
根据ID批量删除:

/**
 * 根据ID批量删除测试
 * 使用方法 int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList)
 */
@Test
void deleteByIdsTest() {
    Long[] array = {1L, 3L, 9L};
    List<Long> ids = Arrays.stream(array).collect(Collectors.toList());
    int result = userMapper.deleteBatchIds(ids);
    logger.info("result: " + result);
}

控制台打印的日志:
21
user表id为1、3、9的记录已成功删除::
22
根据entity条件删除:

/**
 * 根据entity条件删除测试
 * 使用方法 int delete(@Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void deletedTest() {
    User user = new User().setGender("女").setAge(22);
    // WHERE gender=? AND age=?
    QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
    int result = userMapper.delete(queryWrapper);
    logger.info("result: " + result);
}

控制台打印的日志:
23
user表gender为女并且age为22的记录已成功删除:
24
删除所有(测试之前先复制user表):

/**
 * 删除所有测试,queryWrapper为null
 * 使用方法 int delete(@Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void deleteAllTest() {
    int result = userMapper.delete(null);
    logger.info("result: " + result);
}

控制台打印的日志:
25
user表所有记录已成功删除:
26
根据columnMap条件删除(将刚才复制的表重命名为user):

/**
 * 根据columnMap条件删除测试
 * 使用方法 int deleteByMap(@Param("cm") Map<String, Object> columnMap)
 */
@Test
void deletedByMapTest() {
    HashMap<String, Object> map = new HashMap<>();
    // WHERE user_level = ? AND user_point = ?
    map.put("user_level", Byte.valueOf("4"));
    map.put("user_point", 3000);
    int result = userMapper.deleteByMap(map);
    logger.info("result: " + result);
}

控制台打印的日志:
27
user表user_level为4并且user_point为3000的记录已成功删除:
28

3.更新方法

根据ID更新:

/**
 * 根据ID更新测试
 * 使用方法 int updateById(@Param("et") T entity)
 */
@Test
void updateByIDTest() {
    User user = new User().setId(2L).setPassword("aa123bbd678").setUserPoint(1300).setUserLevel(Byte.valueOf("3"));
    int result = userMapper.updateById(user);
    logger.info("result: " + result);
}

控制台打印的日志:
29
user表id为2的记录的password、user_point、user_level字段已成功更新:
30
根据whereWrapper条件更新,使用entity进行set,queryWrapper构造条件:

/**
 * 根据whereWrapper条件更新测试1,使用entity进行set,queryWrapper构造条件
 * 使用方法 int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper)
 */
@Test
void updateByWrapperTest1() {
    // UPDATE user SET user_point=?, user_level=? WHERE (gender = ?)
    User user = new User().setUserPoint(3200).setUserLevel(Byte.valueOf("4")).setBirthday(null);
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("gender", "男");
    int result = userMapper.update(user, queryWrapper);
    logger.info("result: " + result);
}

控制台打印的日志:
31
user表gender为男的记录的user_point和user_level字段已成功更新:
32
使用queryWrapper更新的方式,只能将entity中不为null的属性更新到表中,而使用updateWrapper进行更新,可以将字段更新为null:

/**
 * 根据whereWrapper条件更新测试2,entity为null,使用updateWrapper构造条件并set
 * 使用方法 int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper)
 */
@Test
void updateByWrapperTest2() {
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    // UPDATE user SET user_point=?,user_level=?,birthday=? WHERE (gender = ?)
    updateWrapper.eq("gender", "女").set("user_point", 12000).set("user_level", Byte.valueOf("6")).set("birthday",
        null);
    int result = userMapper.update(null, updateWrapper);
    logger.info("result: " + result);
}

控制台打印的日志:
33
user表gender为女的记录的user_point、user_level、birthday字段已成功更新:
34

4.查询方法

根据ID查询:

/**
 * 根据ID查询测试
 * 使用方法 T selectById(Serializable id)
 */
@Test
void selectByIdTest() {
    User user = userMapper.selectById(6L);
    logger.info(user.toString());
}

控制台打印的日志:
35
根据ID批量查询:

/**
 * 根据ID批量查询测试
 * 使用方法 List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList)
 */
@Test
void selectByIdsTest() {
    Long[] array = {1L, 5L, 9L, 11L};
    List<Long> ids = Arrays.stream(array).collect(Collectors.toList());
    List<User> users = userMapper.selectBatchIds(ids);
    users.forEach(user -> logger.info(user.toString()));
}

控制台打印的日志:
36
根据条件查询一条记录:

/**
 * 根据条件查询一条记录测试
 * 使用方法 T selectOne(@Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void selectOneTest() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // WHERE (realname = ?)
    queryWrapper.eq("realname", "貂蝉");
//    queryWrapper.eq("gender", "女");
    User user = userMapper.selectOne(queryWrapper);
    logger.info(user.toString());
}

控制台打印的日志:
37
如果查询结果超出一条记录会出错。将条件改为:

queryWrapper.eq("gender", "女");

然后进行测试,会报如下错误:
38
根据条件查询全部记录:

/**
 * 根据条件查询全部记录测试
 * 使用方法 List<T> selectList(@Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void selectListTest() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // SELECT realname,age,user_point,user_level FROM user WHERE (age BETWEEN ? AND ? OR user_point < ?)
    queryWrapper.select("realname", "age", "user_point", "user_level").between("age", 21, 24).or().lt("user_point",
        500);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(user -> logger.info(user.toString()));
}

控制台打印的日志:
39
根据columnMap条件查询:

/**
 * 根据columnMap条件查询测试
 * 使用方法 List<T> selectByMap(@Param("cm") Map<String, Object> columnMap)
 */
@Test
void selectByMapTest() {
    HashMap<String, Object> map = new HashMap<>();
    // WHERE gender = ? AND age = ?
    map.put("gender", "男");
    map.put("age", 25);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(user -> logger.info(user.toString()));
}

控制台打印的日志:
40
根据Wrapper条件查询全部记录:

/**
 * 根据Wrapper条件查询全部记录测试
 * 使用方法 List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void selectMapsTest() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // SELECT realname,age,user_point,user_level FROM user WHERE (age BETWEEN ? AND ? OR user_point < ?)
    queryWrapper.select("realname", "age", "user_point", "user_level").between("age", 21, 24).or().lt("user_point",
        500);
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(map -> logger.info(map.toString()));
}

控制台打印的日志:
41
根据Wrapper条件查询全部记录,只返回第一个字段的值:

/**
 * 根据Wrapper条件查询全部记录测试,只返回第一个字段的值
 * 使用方法 List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void selectObjsTest() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // SELECT username,password,realname FROM user WHERE (age > ?)
    queryWrapper.select("username", "password", "realname").gt("age", 25);
    // 只返回第一个字段username
    List<Object> objects = userMapper.selectObjs(queryWrapper);
    objects.forEach(object -> logger.info(object.toString()));
}

控制台打印的日志:
42
根据Wrapper条件查询总记录数:

/**
 * 根据Wrapper条件查询总记录数测试
 * 按用方法 Integer selectCount(@Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void selectCountTest() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // WHERE (user_point > ? AND age < ?)
    queryWrapper.gt("user_point", 1000).lt("age", 25);
    Integer count = userMapper.selectCount(queryWrapper);
    logger.info("count: " + count);
}

控制台打印的日志:
43

五、分页查询

创建配置类并配置分页插件:

package com.rtxtitanv.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.config.MybatisPlusConfig
 * @description MybatisPlusConfig
 * @date 2021/5/19 17:19
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 配置分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

查询所有记录并分页:

/**
 * 分页查询测试1,queryWrapper为null,查询所有记录并分页
 * 使用方法 <E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void selectPageTest1() {
    // 参数1:当前页,默认为1,小于1则按1处理,参数2:每页记录数,默认为10
    Page<User> page = new Page<>(3, 3);
    Page<User> userPage = userMapper.selectPage(page, null);
    logger.info("查询到的记录总数:" + userPage.getTotal());
    logger.info("总页数:" + userPage.getPages());
    // 取出分页数据
    List<User> users = userPage.getRecords();
    users.forEach(user -> logger.info(user.toString()));
}

控制台打印的日志:
44
根据条件查询全部记录并分页:

/**
 * 分页查询测试2,根据条件查询全部记录并分页
 * 使用方法 <E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper)
 */
@Test
void selectPageTest2() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // WHERE (gender = ? AND age >= ?)
    queryWrapper.eq("gender", "男").ge("age", 25);
    // 参数1:当前页,默认为1,小于1则按1处理,参数2:每页记录数,默认为10
    Page<User> page = new Page<>(1, 2);
    Page<User> userPage = userMapper.selectPage(page, queryWrapper);
    logger.info("查询到的记录总数:" + userPage.getTotal());
    logger.info("总页数:" + userPage.getPages());
    // 取出分页数据
    List<User> users = userPage.getRecords();
    users.forEach(user -> logger.info(user.toString()));
}

控制台打印的日志:
45

六、主键生成策略

通过@TableId注解的type属性可以指定主键生成策略,默认值为IdType.NONEtype的值取为枚举类IdType中定义的枚举:

package com.baomidou.mybatisplus.annotation;

import lombok.Getter;

@Getter
public enum IdType {
    /**
     * 数据库ID自增
     * <p>该类型请确保数据库设置了ID自增,否则无效</p>
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于INPUT)
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID为空,才自动填充 */
    /**
     * 分配ID(主键类型为number或string)
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
     *
     * @since 3.3.0
     */
    ASSIGN_ID(3),
    /**
     * 分配UUID(主键类型为string)
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
     */
    ASSIGN_UUID(4);

    private final int key;

    IdType(int key) {
        this.key = key;
    }
}

1.AUTO

数据库ID自增,前提是表中主键要设置为自增。主键生成策略设置为AUTO:

@TableId(value = "id", type = IdType.AUTO)
private Long id;

运行测试方法insertTest()插入一条记录(不用设置主键),发现插入的记录主键自增1:
46

2.NONE

无状态,该类型为未设置主键类型。注解里等于跟随全局,全局里约等于INPUT。而全局主键生成策略默认值为ASSIGN_ID,如果没有主键生成策略,默认根据雪花算法生成。主键生成策略设置为NONE:

@TableId(value = "id", type = IdType.NONE)
private Long id;

通过配置项mybatis-plus.global-config.db-config.id-type可以设置全局主键生成策略,默认值为ASSIGN_ID。如果全局策略和局部策略同时设置,局部策略优先级高。

运行测试方法insertTest()插入一条记录(不用设置主键),此时没有设置全局策略,默认根据雪花算法生成主键:
47
type属性的默认值为IdType.NONE,而注解里的NONE跟随全局主键生成策略,全局主键生成策略默认值为ASSIGN_ID。在没有设置type属性也没有设置全局主键生成策略的情况下,默认根据雪花算法生成主键。未设置type属性:

@TableId(value = "id")
private Long id;

运行测试方法insertTest()插入一条记录(不用设置主键),此时没有设置全局策略,默认根据雪花算法生成主键:
48

3.INPUT

手动设置主键值。主键生成策略设置为INPUT:

@TableId(value = "id", type = IdType.INPUT)
private Long id;

insert前自行set主键值:

/**
 * 插入一条记录测试
 * 使用方法 int insert(T entity)
 */
@Test
void insertTest() {
    User user = new User();
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime localDateTime = LocalDateTime.parse("1993-10-16 20:36:10", dateTimeFormatter);
    user.setId(20L).setUsername("kong1ming").setPassword("mkg568er7k").setRealname("诸葛亮").setGender("男").setAge(27)
        .setEmail("kongming@xxx.com").setUserPoint(6000).setUserLevel(Byte.valueOf("5")).setBirthday(localDateTime);
    // 返回的result是受影响的行数,并不是自增后的ID
    int result = userMapper.insert(user);
    logger.info("result: " + result);
    // 自增后的ID会回填到对象中
    logger.info("新增加的一条记录的ID:" + user.getId());
}

运行测试方法insertTest()插入一条记录,发现插入的记录主键为手动设置的值:
49

4.ASSIGN_ID

分配ID,主键类型为Number(Long和Integer)或String,默认实现为雪花算法。主键生成策略设置为ASSIGN_ID:

@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;

运行测试方法insertTest()插入一条记录(不用设置主键),发现生成一个全局唯一ID作为插入记录的主键:
50

5.ASSIGN_UUID

分配UUID,主键类型为String,生成的UUID不带下划线。主键生成策略设置为ASSIGN_UUID:

@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;

修改user表中主键id的类型:
51
运行测试方法insertTest()插入一条记录(不用设置主键),发现生成一个不带下划线的UUID作为插入记录的主键:
52

七、条件构造器

QueryWrapperLambdaQueryWrapper)和UpdateWrapperLambdaUpdateWrapper)的父类AbstractWrapper用于生成sql的where条件,entity 属性也用于生成sql的where条件。注意,entity生成的where条件与使用各个api(条件构造器)生成的where条件没有任何关联行为。通过以下方式可以创建条件构造器对象用来构造where条件:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
QueryWrapper<User> queryWrapper = new QueryWrapper<>(entity);
QueryWrapper<User> queryWrapper = Wrappers.query();
QueryWrapper<User> queryWrapper = Wrappers.query(entity);

// lambda方式
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>(entity);
LambdaQueryWrapper<User> lambdaQueryWrapper = Wrappers.lambdaQuery();
LambdaQueryWrapper<User> lambdaQueryWrapper = Wrappers.lambdaQuery(entity);
LambdaQueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<User>().lambda();

// 构造update语句的where条件
UpdateWrapper<User> UpdateWrapper = new UpdateWrapper<>();
UpdateWrapper<User> updateWrapper = Wrappers.update();
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
LambdaUpdateWrapper<User> lambdaUpdateWrapper = Wrappers.lambdaUpdate();

下面对条件构造器中的部分条件进行简单归类和使用测试。

1.比较条件

Api描述示例
allEq全部eq(或个别isNull)allEq({id:1,name:"老王",age:null})—>id = 1 and name = '老王' and age is nullallEq({id:1,name:"老王",age:null}, false)—>id = 1 and name = '老王'
isNull字段 IS NULLisNull("name")—>name is null
isNotNull字段 IS NOT NULLisNotNull("name")—>name is not null
eq等于 =eq("name", "老王")—>name = '老王'
ne不等于 <>ne("name", "老王")—>name <> '老王'
gt大于 >gt("age", 18)—>age > 18
ge大于等于 >=ge("age", 18)—>age >= 18
lt小于 <lt("age", 18)—>age < 18
le小于等于 <=le("age", 18)—>age <= 18
betweenBETWEEN 值1 AND 值2between("age", 18, 30)—>age between 18 and 30
notBetweenNOT BETWEEN 值1 AND 值2notBetween("age", 18, 30)—>age not between 18 and 30
in字段 IN (value.get(0), value.get(1), …) 或 字段 IN (v0, v1, …)in("age",{1,2,3})—>age in (1,2,3)in("age", 1, 2, 3)—>age in (1,2,3)
notIn字段 NOT IN (value.get(0), value.get(1), …) 或 字段 NOT IN (v0, v1, …)notIn("age",{1,2,3})—>age not in (1,2,3)notIn("age", 1, 2, 3)—>age not in (1,2,3)
inSql字段 IN ( sql语句 )inSql("age", "1,2,3,4,5,6")—>age in (1,2,3,4,5,6)inSql("id", "select id from table where id < 3")—>id in (select id from table where id < 3)
notInSql字段 NOT IN ( sql语句 )notInSql("age", "1,2,3,4,5,6")—>age not in (1,2,3,4,5,6)notInSql("id", "select id from table where id < 3")—>id not in (select id from table where id < 3)

创建封装用户查询条件的类:

package com.rtxtitanv.model.query;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.List;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.model.query.UserQuery
 * @description 封装用户查询条件类
 * @date 2021/5/20 15:25
 */
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserQuery {
    private Long idQuery;
    private String usernameQuery;
    private String passwordQuery;
    private String realnameQuery;
    private String genderQuery;
    private Integer ageQuery;
    private Integer minAgeQuery;
    private Integer maxAgeQuery;
    private String emailQuery;
    private Integer minUserPointQuery;
    private Integer maxUserPointQuery;
    private Byte userLevelQuery;
    private List<Long> idsQuery;
    private List<Integer> agesQuery;
    private List<Integer> userPointsQuery;
    private List<Byte> userLevelsQuery;
}

测试方法:

/**
 * 条件构造器测试1
 * allEq(boolean condition, Map<R, V> params, boolean null2IsNull):全部eq,或个别isNull
 * 参数说明:
 * condition:表示该条件是否加入最后生成的sql中,true表示加入,false表示不加入
 * params:key为数据库字段名,value为字段值
 * null2IsNull:为true则在map的value为null时调用isNull方法,为false时则忽略value为null的
 */
@Test
void selectByWrapperTest1() {
    // 手动模拟查询条件
    UserQuery userQuery = new UserQuery().setGenderQuery("男").setAgeQuery(25);
    // userQuery为null会查询所有记录
    if (userQuery != null) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        HashMap<String, Object> map = new HashMap<>();
        map.put("gender", userQuery.getGenderQuery());
        map.put("age", userQuery.getAgeQuery());
        map.put("birthday", null);
        // gender不为null或空,age不为null,WHERE (gender = ? AND age = ?)
        // gender不为null或空,age为null,WHERE (gender = ?)
        // gender为null或空,age不为null,WHERE (age = ?)
        // gender为null或空,age为null,没有条件
        queryWrapper.allEq(
            !StringUtils.isNullOrEmpty(userQuery.getGenderQuery()) || userQuery.getAgeQuery() != null, map, false);
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(user -> logger.info(user.toString()));
    } else {
        List<User> users = userMapper.selectList(null);
        users.forEach(user -> logger.info(user.toString()));
    }
}

通过condition参数可以动态地拼接查询条件。

控制台打印的日志:
53
userQuerynull会查询所有记录,控制台打印的日志:
54
ageQuery改为null后,运行该测试方法,控制台打印的日志:
55
allEqnull2IsNull参数为false时会忽略value为null的字段。将null2IsNull设为true后,value为null时会调用isNull方法。运行该测试方法,控制台打印的日志:
56
测试方法:

/**
 * 条件构造器测试2
 * ne(boolean condition, R column, Object val):不等于 <>
 * gt(boolean condition, R column, Object val):大于 >
 * le(boolean condition, R column, Object val):小于等于 <=
 * in(boolean condition, R column, Collection<?> coll):字段 IN (value.get(0), value.get(1), ...)
 * 多个条件默认使用and连接
 */
@Test
void selectByWrapperTest2() {
    // 手动模拟查询条件
    Long[] array = {1L, 5L, 7L, 9L};
    UserQuery userQuery = new UserQuery().setRealnameQuery("张飞").setMinAgeQuery(20).setMaxUserPointQuery(5000)
        .setIdsQuery(Arrays.stream(array).collect(Collectors.toList()));
    if (userQuery != null) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 只有userQuery.getRealnameQuery()不为null或空才会拼接realname <> ?
        queryWrapper
            .ne(!StringUtils.isNullOrEmpty(userQuery.getRealnameQuery()), "realname", userQuery.getRealnameQuery())
            // 只有userQuery.getMinAgeQuery()不为null才会拼接age > ?
            .gt(userQuery.getMinAgeQuery() != null, "age", userQuery.getMinAgeQuery())
            // 只有userQuery.getMaxUserPointQuery()不为null才会拼接user_point <= ?
            .le(userQuery.getMaxUserPointQuery() != null, "user_point", userQuery.getMaxUserPointQuery())
            // 只有userQuery.getIdsQuery()不为null或空才会拼接id IN (?,?,?,?)
            .in(userQuery.getIdsQuery() != null && !userQuery.getIdsQuery().isEmpty(), "id",
                userQuery.getIdsQuery());
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(user -> logger.info(user.toString()));
    } else {
        List<User> users = userMapper.selectList(null);
        users.forEach(user -> logger.info(user.toString()));
    }
}

控制台打印的日志:
57
测试方法:

/**
 * 条件构造器测试3
 * between(boolean condition, R column, Object val1, Object val2):BETWEEN 值1 AND 值2
 * notBetween(boolean condition, R column, Object val1, Object val2):NOT BETWEEN 值1 AND 值2
 * notInSql(R column, String inValue):字段 NOT IN ( sql语句 )
 * 多个条件默认使用and连接,Lambda方式构造条件
 */
@Test
void selectByWrapperTest3() {
    // 手动模拟查询条件
    UserQuery userQuery =
        new UserQuery().setMinAgeQuery(20).setMaxAgeQuery(30).setMinUserPointQuery(2000).setMaxUserPointQuery(5000);
    if (userQuery != null) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 只有userQuery.getMinAgeQuery()不为null且userQuery.getMaxAgeQuery()不为null才会拼接age BETWEEN ? AND ?
        queryWrapper
            .between(userQuery.getMinAgeQuery() != null && userQuery.getMaxAgeQuery() != null, User::getAge,
                userQuery.getMinAgeQuery(), userQuery.getMaxAgeQuery())
            // 只有userQuery.getMinUserPointQuery()不为null且userQuery.getMaxUserPointQuery()不为null才会拼接
            // user_point NOT BETWEEN ? AND ?
            .notBetween(userQuery.getMinUserPointQuery() != null && userQuery.getMaxUserPointQuery() != null,
                User::getUserPoint, userQuery.getMinUserPointQuery(), userQuery.getMaxUserPointQuery())
            // id NOT IN (select id from user where user_level in (1,3))
            .notInSql(User::getId, "select id from user where user_level in (1,3)");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(user -> logger.info(user.toString()));
    } else {
        List<User> users = userMapper.selectList(null);
        users.forEach(user -> logger.info(user.toString()));
    }
}

控制台打印的日志:
58

2.模糊条件

Api描述示例
likeLIKE ‘%值%’like("name", "王")—>name like '%王%'
notLikeNOT LIKE ‘%值%’notLike("name", "王")—>name not like '%王%'
likeLeftLIKE ‘%值’likeLeft("name", "王")—>name like '%王'
likeRightLIKE ‘值%’likeRight("name", "王")—>name like '王%'

测试方法:

/**
 * 条件构造器测试4
 * like(boolean condition, R column, Object val):LIKE '%值%'
 * notLike(boolean condition, R column, Object val):NOT LIKE '%值%'
 * likeLeft(boolean condition, R column, Object val):LIKE '%值'
 * likeRight(boolean condition, R column, Object val):LIKE '值%'
 * 多个条件默认使用and连接,Lambda方式构造条件
 */
@Test
void selectByWrapperTest4() {
    // 手动模拟查询条件
    UserQuery userQuery =
        new UserQuery().setUsernameQuery("ao").setPasswordQuery("so").setRealnameQuery("乔").setEmailQuery("xi");
    if (userQuery != null) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 只有userQuery.getUsernameQuery()不为null或空才会拼接username LIKE ?,Parameters:%val%
        queryWrapper
            .like(!StringUtils.isNullOrEmpty(userQuery.getUsernameQuery()), User::getUsername,
                userQuery.getUsernameQuery())
            // 只有userQuery.getPasswordQuery()不为null或空才会拼接password NOT LIKE ?,Parameters:%val%
            .notLike(!StringUtils.isNullOrEmpty(userQuery.getPasswordQuery()), User::getPassword,
                userQuery.getPasswordQuery())
            // 只有userQuery.getRealnameQuery()不为null或空才会拼接realname LIKE ?,Parameters:%val
            .likeLeft(!StringUtils.isNullOrEmpty(userQuery.getRealnameQuery()), User::getRealname,
                userQuery.getRealnameQuery())
            // 只有userQuery.getEmailQuery()不为null或空才会拼接email LIKE ?,Parameters:val%
            .likeRight(!StringUtils.isNullOrEmpty(userQuery.getEmailQuery()), User::getEmail,
                userQuery.getEmailQuery());
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(user -> logger.info(user.toString()));
    } else {
        List<User> users = userMapper.selectList(null);
        users.forEach(user -> logger.info(user.toString()));
    }
}

控制台打印的日志:
59

3.多条件连接

Api描述示例
or拼接 OR 或 OR 嵌套eq("id",1).or().eq("name","老王")—>id = 1 or name = '老王'or(i -> i.eq("name", "李白").ne("status", "活着"))—>or (name = '李白' and status <> '活着')
andAND 嵌套and(i -> i.eq("name", "李白").ne("status", "活着"))—>and (name = '李白' and status <> '活着')
nested正常嵌套 不带 AND 或者 ORnested(i -> i.eq("name", "李白").ne("status", "活着"))—>(name = '李白' and status <> '活着')

测试方法:

/**
 * 条件构造器测试5
 * ge(boolean condition, R column, Object val):大于等于 >=
 * lt(boolean condition, R column, Object val):小于 <
 * eq(boolean condition, R column, Object val):等于 =
 * or():拼接 OR
 * or(boolean condition, Consumer<Children> consumer):OR 嵌套
 * 主动调用or表示紧接着下一个方法不是用and连接,不调用or则默认为使用and连接
 * Lambda方式构造条件
 */
@Test
void selectByWrapperTest5() {
    // 手动模拟查询条件
    Byte[] bytes = {2, 3, 5, 6};
    List<Byte> user_levels = Arrays.stream(bytes).collect(Collectors.toList());
    UserQuery userQuery = new UserQuery().setMinUserPointQuery(5000).setMaxAgeQuery(20)
        .setUserLevelsQuery(user_levels).setGenderQuery("男");
    if (userQuery != null) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 只有userQuery.getMinUserPointQuery()不为null才会拼接user_point >= ?
        queryWrapper
            .ge(userQuery.getMinUserPointQuery() != null, User::getUserPoint, userQuery.getMinUserPointQuery())
            // OR
            .or()
            // 只有userQuery.getMaxAgeQuery()不为null才会拼接age < ?
            .lt(userQuery.getMaxAgeQuery() != null, User::getAge, userQuery.getMaxAgeQuery())
            // 只有userQuery.getUserLevelsQuery()不为null或空才会拼接OR (user_level NOT IN (?,?,?,?))
            // 同时只有userQuery.getGenderQuery()不为null或空才会拼接OR (user_level NOT IN (?,?,?,?) AND gender = ?)
            .or(userQuery.getUserLevelsQuery() != null && !userQuery.getUserLevelsQuery().isEmpty(),
                i -> i.notIn(User::getUserLevel, userQuery.getUserLevelsQuery()).eq(
                    !StringUtils.isNullOrEmpty(userQuery.getGenderQuery()), User::getGender,
                    userQuery.getGenderQuery()));
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(user -> logger.info(user.toString()));
    } else {
        List<User> users = userMapper.selectList(null);
        users.forEach(user -> logger.info(user.toString()));
    }
}

控制台打印的日志:
60

4.排序

Api描述示例
orderByAsc排序:ORDER BY 字段, … ASCorderByAsc("id", "name")—>order by id ASC,name ASC
orderByDesc排序:ORDER BY 字段, … DESCorderByDesc("id", "name")—>order by id DESC,name DESC
orderBy排序:ORDER BY 字段, …orderBy(true, true, "id", "name")—>order by id ASC,name ASC

测试方法:

/**
 * 条件构造器测试6
 * orderByAsc(R column):ORDER BY 字段, ... ASC
 * orderByDesc(R column):ORDER BY 字段, ... DESC
 * orderBy(boolean condition, boolean isAsc, R... columns):ORDER BY 字段, ...
 * 部分参数说明:
 * isAsc:是否升序排序,true表示是,false表示否
 * Lambda方式构造条件
 */
@Test
void selectByWrapperTest6() {
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
//    // ORDER BY age ASC
//    queryWrapper.orderByAsc(User::getAge);
//    // ORDER BY user_point DESC
//    queryWrapper.orderByDesc(User::getUserPoint);
    // ORDER BY birthday ASC
    queryWrapper.orderBy(true, true, User::getBirthday);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(user -> logger.info(user.toString()));
}

单独测试queryWrapper.orderByAsc(User::getAge),控制台打印的日志:
61
单独测试queryWrapper.orderByDesc(User::getUserPoint),控制台打印的日志:
62
单独测试queryWrapper.orderBy(true, true, User::getBirthday),控制台打印的日志:
63

5.select

在Mybatis-Plus中,默认查询所有字段,若只需要查询部分字段,可以通过select方法指定字段。测试方法:

/**
 * 条件构造器测试7
 * select(String... columns):设置查询字段
 * select(SFunction<T, ?>... columns):Lambda方式设置查询字段
 */
@Test
void selectByWrapperTest7() {
//    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//    queryWrapper.select("id", "realname", "age").in(true, "id", 1L, 3L, 5L);
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    // SELECT id,realname,age FROM user WHERE (id IN (?,?,?))
    queryWrapper.select(User::getId, User::getRealname, User::getAge).in(true, User::getId, 2L, 4L, 6L);
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(map -> logger.info(map.toString()));
}

通过queryWrapper.select("id", "realname", "age")的方式查询id为1、3、5的指定字段的记录,控制台打印的日志:
64
通过Lambda方式查询id为2、4、6的指定字段的记录,控制台打印的日志:
65
通过select方法还可以过滤查询字段。测试方法:

/**
 * 条件构造器测试8
 * select(Class<T> entityClass, Predicate<TableFieldInfo> predicate):过滤查询字段,主键除外
 * Lambda方式构造条件
 */
@Test
void selectByWrapperTest8() {
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    // SELECT id,username,realname,gender,age,email,user_point,user_level FROM user WHERE (id IN (?,?,?))
    queryWrapper.select(User.class, i -> !i.getColumn().equals("password") && !i.getColumn().equals("birthday"))
        .in(true, User::getId, 3L, 6L, 9L);
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(map -> logger.info(map.toString()));
}

控制台打印的日志:
66

6.分组和过滤

Api描述示例
groupBy分组:GROUP BY 字段, …groupBy("id", "name")—>group by id,name
havingHAVING ( sql语句 )having("sum(age) > 10")—>having sum(age) > 10having("sum(age) > {0}", 11)—>having sum(age) > 11

测试方法:

/**
 * 条件构造器测试9
 * groupBy(R column):GROUP BY 字段, ...
 * having(boolean condition, String sqlHaving, Object... params):HAVING ( sql语句 )
 */
@Test
void selectByWrapperTest9() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    // SELECT user_level,GROUP_CONCAT(realname) AS realname_concat,COUNT(*) AS count,SUM(user_point) AS
    // point_sum,MAX(age) AS max_age FROM user GROUP BY user_level HAVING point_sum < 5000 OR max_age > 30
    queryWrapper
        .select("user_level", "GROUP_CONCAT(realname) AS realname_concat", "COUNT(*) AS count",
            "SUM(user_point) AS point_sum", "MAX(age) AS max_age")
        .groupBy("user_level").having(true, "point_sum < 5000 OR max_age > 30");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(map -> logger.info(map.toString()));
}

控制台打印的日志:
67

八、逻辑删除

逻辑删除就是在表中加一个用来表示记录是否被删除的字段,用1来表示该记录已被删除。逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。如果是需要频繁查出来看的数据就不应使用逻辑删除,而是以一个状态去表示。逻辑删除对插入不做限制。查询会追加where条件过滤掉已删除数据,且使用wrapper.entity生成的where条件会忽略该字段。更新会追加where条件防止更新到已删除数据,且使用wrapper.entity生成的where条件会忽略该字段。删除会转变为更新。

首先在user表中新增一个deleted字段,表示该记录是否被删除,默认值为0,表示没被删除,1表示已被删除:
68
application.yml中新增以下配置:

mybatis-plus:
  global-config:
    db-config:
#      # 全局逻辑删除的实体字段名
#      logic-delete-field: deleted
      # 逻辑已删除值,默认为1
      logic-delete-value: 1
      # 逻辑未删除值,默认为0
      logic-not-delete-value: 0

上面注释掉的logic-delete-field为全局逻辑删除的实体字段名,如果设置了全局逻辑删除的实体字段名,在实体类中与逻辑删除字段映射的实例域上可以不用加@TableLogic

在User实体类新增以下实例域,并加上@TableLogic注解:

@TableLogic
private Integer deleted;

运行测试方法deleteByIdsTest删除id为1、3、9的记录,查看控制台打印的日志发现执行的是UPDATE将deleted字段设置为1:
69
刷新user表,发现id为1、3、9的deleted字段被更新为1:
70
在配置了逻辑删除后,查询会追加where条件过滤掉已删除数据,且使用wrapper.entity生成的where条件会忽略该字段。测试方法:

/**
 * 逻辑删除后查询测试
 * 查询时会追加where条件过滤掉已删除数据且使用entity生成的where条件会忽略逻辑删除字段
 */
@Test
void selectAfterLogicDeleteTest() {
    // SELECT id,username,password,realname,gender,age,email,user_point,user_level,birthday,deleted FROM user WHERE
    // gender=? AND age=? AND deleted=0
    QueryWrapper<User> queryWrapper = new QueryWrapper<>(new User().setGender("女").setAge(22).setDeleted(1));
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(user -> logger.info(user.toString()));
}

运行该测试方法,查看控制台打印的日志发现追加了WHERE deleted=0过滤已删除记录并且使用entity生成的where条件忽略了deleted字段:
71

九、自动填充

如果在插入和更新表记录的时候不想手动设置某些字段的值,比如创建时间和修改时间这种字段,可以通过Mybatis-Plus的自动填充功能在插入和更新时对这些字段进行填充,从而不需要手动设置它们的值。

在user表中新增create_time和update_time字段:
72
在User实体类新增以下实例域,并通过@TableFieldfill属性指定字段自动填充策略:

@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;

字段自动填充策略:

  • FieldFill.DEFAULT:默认不处理。
  • FieldFill.INSERT:插入时填充字段。
  • FieldFill.UPDATE:更新时填充字段。
  • FieldFill.INSERT_UPDATE:插入和更新时填充字段。

自定义MetaObjectHandler实现类:

package com.rtxtitanv.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.handler.MyMetaObjectHandler
 * @description MyMetaObjectHandler
 * @date 2021/5/21 14:22
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // 插入时自动填充策略
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 更新时自动填充策略
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
    }
}

运行测试方法insertTest(),控制台打印的日志:
73
刷新user表,新插入的记录的create_time和update_time都成功填充了值:
74

十、乐观锁

当要更新一条记录的时候,希望这条记录没有被别人更新。

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时,set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

在user表中新增一个version字段,默认值为1:
75
配置乐观锁插件:

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 配置乐观锁插件
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

在User实体类新增以下实例域,并加上@Version注解:

@Version
private Integer version;

测试方法:

/**
 * 乐观锁测试
 */
@Test
void optimisticLockerTest() {
    User user = userMapper.selectById(11L);
    user.setPassword("123edf321").setAge(29);
    int result = userMapper.updateById(user);
    logger.info("result: " + result);
}

查看控制台打印的日志,发现生成的UPDATE语句追加了where条件version=?,其中传递给占位符?的参数就是更新之前查询出的version,如果更新时的version不等于之前获取的version,则会更新失败,而这里更新时的version等于之前获取的version,更新成功:
76
刷新user表发现id为11的记录的version字段成功更新为2:
77

代码示例

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RtxTitanV

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值