简介

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

我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。

参考资料

Mybatis-Plus

快速集成

  • 环境集成配置注意事项
    • 需要配置数据的基本信息 application.yml
    • pom.xml 中不要用 <packaging>pom</packaging> 会导致resources目录下的文件无法编译到 classes 下 (会提示找不到数据库之类的
    • 启动入口一定要配置 @MapperScan("com.adalucky.MybatisPlus.modules") // 需要扫描的包 不然会提示找不到 bean
    • 注意测试类的目录层级要和 java 中层级保持一致,且测试类没有配置文件时使用的是 main 的配置,如果有的话就必须定义数据库等信息使用自己单独的数据源和配置
    • Mybatis-Plus 默认是开启了下划线和驼峰互转的,也就是说 如果我们在实体类中定义了属性叫做 userName 那么它执行的时候查询的列会变为 user_name
1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/data_auto_endpoint?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: test
password: 123456
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.adalucky</groupId>
<artifactId>Mybatis-Plus</artifactId>
<version>1.0.0</version>
<name>Mybatis-Plus</name>
<!-- 会影响 resources 下编译到 classes 下 <packaging>pom</packaging>-->
<description>这是一个关于Mybatis-Plus的学习项目</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- java 连接 mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<!--swagger http://localhost:8080/dev/doc.html -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<!--在引用时请在maven中央仓库搜索3.X最新版本号-->
<version>3.0.3</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>

</project>

自动填充

  • 有些数据需要设置为自动填充,比如创建时间、更新时间(当然也可以在数据层面约束)
  • 自动填充:@TableField(fill = FieldFill.xxx)针对创建时间和更新时间可以使用自动填充(当然也可以在数据里面进行设置相应策略)
  • FieldFill.INSERT: 插入数据时
  • FieldFill.INSERT_UPDATE: 插入和更新
  • 实现类和实体类时间都是用的 LocalDateTime
1
2
3
4
5
6
7
//  创建时间
@TableField(select = false,fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 更新时间
//@TableField(select = false,fill = FieldFill.INSERT_UPDATE)
@TableField(select = false,fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
  • 创建 MyMetaObjectHandler.java 实现 MetaObjectHandler 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.adalucky.MybatisPlus.common.handler;

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

import java.time.LocalDateTime;

/**
* @ClassName MyMetaObjectHandler
* @Description Mybatis-Plus 自动填充创建时间和更新时间为本地时间
* @Author ada
* @Computer Mac mini
* @Date 2022/3/7 16:56
* @JDKVersion JDK1.8
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
*实体中的日期也需要为 LocalDate 类型
* createTime、updateTime 需要和实体中的保持一致,实体中的需要和数据库映射正确
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill createTime....");
this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
/* this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐) 这行注释了因为插入的时候我不想生成更新时间*/
}

@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill updateTime....");
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
}
}

自动填充时间

@version

简介

  • 标记乐观锁,通过 version 字段来保证数据的安全性,当修改数据的时候,会添加 version 作为条件,当条件成立的时候才会修改成功
  • 原理:数据库中新增一个 version 字段,两个线程同时去操作数据是会加上 version 这个字段的值,操作完成后就会修改这个 version 值,可以保证线程安全
  • 多线程并发案例:线程执行前为 1,其中一个执行成功后会修改 version 那么另外一个就查询不到也就不会去修改了
    • 线程一:update…..set version = 2 where version = 1
    • 线程二:update…..set version = 2 where version = 1

示例

  • 数据库表内添加 version 字段 int 类型,设置默认值为 1
  • 实体中新增 version 成员变量,并添加 @version 注解
1
2
@Version
private Integer version;
  • 新增配置类 MyBatisPlusConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.adalucky.MybatisPlus.config;

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

/**
* @ClassName MyBatisPlusConfig
* @Description MyBatis-Plus 配置类
* @Author ada
* @Computer Mac mini
* @Date 2022/1/28 18:25
* @JDKVersion JDK1.8
*/
@Configuration
public class MyBatisPlusConfig {
// 最新版 分页插件 DbType.MYSQL 指定为自己的方言
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

/**
* 乐观锁
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptorVerion() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}

乐观锁示例

@通用枚举

简介

  • 有些字段在数据库中的存储为数字,但是希望返回给前端的是汉字,比如用户的性别在数据库中:sex=0 (0女 1男 2保密) 期望返回的是汉字不是数字
  • 注意不能给实体类中的字段设置成 @TableField(select = false) 这样不会去查询,所以返回的都会是 null
  • 如果需要赋值比如更新或者插入数据也可以通过枚举进行赋值 sysUser.setSex(SexEnums.女);
1
2
3
4
5
6
@Test
void save(){
SysUser sysUser = new SysUser();
sysUser.setSex(SexEnums.女);
mapper.insert(sysUser);
}

方式一:通过注解实现

  • 数据库中有个 sex 字段,类型为 int,默认值为 2
  • 创建枚举类 SexEnums 属性上加上 @EnumValue 注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.adalucky.MybatisPlus.common.enums.mysql;

import com.baomidou.mybatisplus.annotation.EnumValue;

/**
* @ClassName SexEnums
* @Description 数据库枚举,当数据库中的 sex =1 我们映射为男,sex=2,映射为女
* @Author ada
* @Computer Mac mini
* @Date 2022/3/7 16:56
* @JDKVersion JDK1.8
*/

public enum SexEnums {
// code=1时候映射成 sex1 ,变量要加 @EnumValue 注解
女(0,"当 sex=0 映射成女"),男(1,"当 sex=2 映射成男"), 保密(2,"当 sex=2 映射成保密");


SexEnums(Integer code,String msg){
this.code = code;
this.msg = msg;
}
@EnumValue
private final int code;
private final String msg;
}
  • 配置 application.yml(com.adalucky.MybatisPlus.common.enums.mysql 为枚举类所在的目录)
1
2
mybatis-plus:
type-enums-package: com.adalucky.MybatisPlus.common.enums.mysql
  • 实体类定义时类型为枚举类对应的类型
1
private SexEnums sex;

通过注解实现

方式二:通过实现接口

  • 配置 application.yml(com.adalucky.MybatisPlus.common.enums.mysql 为枚举类所在的目录)和上面是一致的
1
2
mybatis-plus:
type-enums-package: com.adalucky.MybatisPlus.common.enums.mysql
  • 实体类定义时类型为枚举类对应的类型
1
private StatusEnums status;
  • 定义枚举类实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.adalucky.MybatisPlus.common.enums.mysql;

import com.baomidou.mybatisplus.annotation.IEnum;

/**
* @author ada
* @ClassName StatusEnums
* @Description Status 枚举
* @Computer Macbook pro
* @Date 2022/3/7 21:37
* @JDKVersion JDK1.8
*/
public enum StatusEnums implements IEnum<Integer> {
启用(0, "当 status=0 映射成启用"),
禁用(1, "当 status=1 映射成禁用");

private final Integer code;
private final String msg;

StatusEnums(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

@Override
public Integer getValue() {
return this.code;
}
}

实现接口

逻辑删除

  • Mybatis-Plus推荐使用 3.3.0及以上版本就不需要在实体中对该字段添加@TableLogic注解
  • 查询时会过滤掉逻辑删除的数据,删除时只会逻辑删除,修改 deleted 的值
  • 配置 application.yml文件
1
2
3
4
5
6
7
8
9
mybatis-plus:
type-enums-package: com.adalucky.MybatisPlus.common.enums
global-config:
db-config:
#全局配置,主键自动增长,就不需要在每个实体类上声明了 @TableId(type = IdType.AUTO)
id-type: auto
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0版本以上就不需要在实体中对该字段添加@TableLogic注解)
logic-delete-value: 0 # 逻辑已删除值(默认为 1.我们改为 0)
logic-not-delete-value: 1 # 逻辑未删除值(默认为 0,我们改为 1)

查询自动过滤掉逻辑删除

逻辑删除数据

entity

  • 实体类主要是绑定数据库的
  • 类上指定表名:Mybatis-Plus 默认会用我们的实体类的类名作为数据库表名查询(默认是开启了支持类名和数据之间的驼峰和蛇形命名的转换,如果需要关闭需要去 yml 中配置),如果表名和类名对不上就在类层面加上这个注解可以绑定类和数据库的表,@TableName("sys_user")
  • 指定主键:Mybatis-Plus 默认会将 字段名叫做 id 的当做主键,如果主键字段名不叫 id,可以在对应的字段定义出加上@TableId(type = IdType.AUTO) 申明是一个主键,并且开启自增,type=IdType.AUTO 就是声明主键生成策略,是一个枚举,还有什么雪花算法之类的,我们一般是用自增。不写的话就为 NONE
Key 描述
AUTO 数据库自增,如果开发者自己赋值后数据库依旧会采用自增策略覆盖掉
NONE 无。采用默认的 Mybatis-Plus set 主键,雪花算法实现
INPUT 需要开发者手动赋值(如果开发者没有赋值,传递参数会为 null,但是数据库主键有自己的策略进行自增
ASSIGN_ID Mybatis-Plus自动分配 ID,Long、Integer、String 也是一个雪花算法
ASSIGN_UUID Mybatis-Plus自动分配UUID,String 注意生成的类型为 String 那么声明的实体也需要是 String 其次数据库的类型也要是字符支持的类型,长度
  • 指定字段名:@TableField("nickname") 或者 @TableField(value="nickname") 当字段和数据库字段命名不一致时可以进行绑定

  • 其它表中的字段:@TableField(exist = false) 声明该实体不是表内的字段

  • 指定忽略查询字段: @TableField(select = false)

  • 更多注解参考 官方注解指南

mapper

  • mapper 里面的方法都是以 select update insert delete 命名的,很严谨,因为是直接和数据库交互的 service里面都是 get 什么之类的
  • mapper 是对实体类操作,最终把操作映射到数据库里面,mapper 是一个接口集成 baseMapper,指定泛型为需要操作的实体类对象 需要用@Component注入到 IOC 中
  • BaseMapper 中有 17个(版本不同可能不一样多)已经封装好的增删改查的方法可以直接用 SysUserMapper 去调用
  • SysUserMapper 也是一个接口 但是是动态代理的,丢到虚拟机中,启动运行了以后就会生成该对象
1
2
3
@Component
public interface SysUserMapper extends BaseMapper<SysUser> {
}

insert

insert

  • insert 插入单条数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* insert 插入单条数据
* 返回的是一个 int 类型,成功条数
* 主键设置了自增策略可以不赋值,其它的字段没有做不为空要求
* Execute SQL:INSERT INTO sys_user ( login_name, user_name, sex, status, create_time ) VALUES ( '13512648422', '测试save456', 2, 1, '2022-03-08T17:14:52.816' )
*/
@Test
void insert() {
SysUser sysUser = new SysUser();
sysUser.setLoginName("13512648422");
sysUser.setUserName("测试save456");
sysUser.setSex(SexEnums.SECRET);
sysUser.setStatus(StatusEnums.ENABLE);
mapper.insert(sysUser);
}

Wapper

简介

  • Wapper 就是一个条件构造,我们可以去自定义一些条件查询,上面的查询基本上都是针对主键来的

单条件

  • wrapper.eq
1
2
3
4
5
6
7
8
9
10
11
12
/**
* wrapper.eq 单条件查询
* 根据user_name 查询值为【阿达】的数据,user_name字段名必须和数据字段名一致
* 数据中的字段叫做 user_name 这里可以写成 user_name 或者 user_Name 不支持 userName 会提示找不到列名
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_name = '阿达')
*/
@Test
void selectWrapperEq(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("user_name", "阿达");
System.out.println(mapper.selectList(wrapper));
}

多条件

  • wrapper.allEq
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* wrapper.allEq 多条件查询
* 查询 user_name=阿达 并且 status=1,并且sex=1 的数据
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_name = '阿达' AND sex = 1 AND status = 1)
*/
@Test
void selectWrapperAllEq(){
QueryWrapper wrapper = new QueryWrapper();
Map map = new HashMap();
map.put("user_name", "阿达");
map.put("status", 1);
map.put("sex", 1);
wrapper.allEq(map);
System.out.println(mapper.selectList(wrapper));
}

多条件查询

小于

  • wrapper.lt
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.lt 小于
* 查询 user_id 小于 10005 的数据(不包含等于)
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_id < 10005)
*/
@Test
void selectWrapperLt() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.lt("user_id", 10005);
System.out.println(mapper.selectList(wrapper));
}

大于

  • wrapper.gt 大于
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.gt 大于
* 查询 user_id 大于 10005 的数据(不包含等于)
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_id > 10005)
*/
@Test
void selectWrapperGt() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.gt("user_id", 10005);
System.out.println(mapper.selectList(wrapper));
}

大于等于

  • wrapper.ge 大于等于
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.ge 大于等于
* 查询 user_id 大于等于 10005 的数据(包含等于)
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_id >= 10005)
*/
@Test
void selectWrapperGe() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.ge("user_id", 10005);
System.out.println(mapper.selectList(wrapper));
}

不等于

  • wrapper.ne 不等于
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.ne 不等于
* 查询 user_name 不等于 【阿达】的数据
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_name <> '阿达')
*/
@Test
void selectWrapperNt(){
QueryWrapper wrapper = new QueryWrapper();
wrapper.ne("user_name", "阿达");
System.out.println(mapper.selectList(wrapper));
}

模糊查询

  • wrapper.like 模糊查询
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.like 模糊查询
* 查询 user_name 中包含了【测试】的数据
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_name LIKE '%测试%')
*/
@Test
void selectWrapperLike() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.like("user_name", "测试");
System.out.println(mapper.selectList(wrapper));
}

左模糊查询

  • wrapper.like 左模糊查询
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.like 左模糊查询
* 查询 user_name 中以【测试】结尾的数据
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_name LIKE '%测试')
*/
@Test
void selectWrapperLikeLeft() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.likeLeft("user_name", "测试");
System.out.println(mapper.selectList(wrapper));
}

右模糊查询

  • wrapper.likeRight 右模糊查询
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.likeRight 右模糊查询
* 查询 user_name 中以【测试】开头的数据
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_name LIKE '测试%')
*/
@Test
void selectWrapperLikeRight() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.likeRight("user_name", "测试");
System.out.println(mapper.selectList(wrapper));
}

联合查询

  • wrapper.inSql 联合查询
1
2
3
4
5
6
7
8
9
10
11
12
/**
* wrapper.inSql 联合查询
* 查询 user_id > 10004,status = 1
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_id IN (select user_id from sys_user where user_id > 10004) AND status IN (select status from sys_user where status = 1))
*/
@Test
void selectWrapperInsql() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.inSql("user_id", "select user_id from sys_user where user_id > 10004");
wrapper.inSql("status", "select status from sys_user where status = 1");
System.out.println(mapper.selectList(wrapper));
}

升序

  • wrapper.orderByAsc 升序排列
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.orderByAsc 升序排列
* 查询数据并根据 user_id 升序排列
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' ORDER BY user_id ASC
*/
@Test
void selectWrapperOrderByAsc() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.orderByAsc("user_id");
mapper.selectList(wrapper).forEach(System.out::println);
}

降序

  • wrapper.orderByDesc 降序排列
1
2
3
4
5
6
7
8
9
10
11
/**
* wrapper.orderByDesc 降序排列
* 查询数据并根据 user_id 降序排列
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' ORDER BY user_id DESC
*/
@Test
void selectWrapperOrderByDesc() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.orderByDesc("user_id");
mapper.selectList(wrapper).forEach(System.out::println);
}

降序+过滤

  • wrapper.orderByDesc 降序排列
  • wrapper.having 排序后过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* wrapper.orderByDesc 降序排列
* wrapper.having 排序后过滤
* 查询数据并根据 user_id 降序排列,只保留 status = 1 的数据(StatusEnums.启用 枚举中映射的为 1)
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' HAVING status ORDER BY user_id DESC
*/

@Test
void selectWrapperOrderByDescAndHaving() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.orderByDesc("user_id");
wrapper.having("status", StatusEnums.启用);
mapper.selectList(wrapper).forEach(System.out::println);
}

select

selectList

  • selectList 查询数据
  • 本次为没有条件,查询所有的并遍历打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class SysUserMapperTest {
@Autowired
SysUserMapper mapper;

/**
* selectList 查询数据
* 返回一个 List<T> 查询结果对象集合
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1'
*/
@Test
void selectList() {
//queryWrapper 条件构造器,没有条件就用 null
mapper.selectList(null).forEach(System.out::println);
}
}

selectById

  • selectById 根据主键查询
1
2
3
4
5
6
7
8
9
10
/**
* selectById 根据主键查询
* 返回一个实体对象
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE user_id=10021 AND deleted='1'
*/
@Test
void selectById(){
SysUser sysUser = mapper.selectById(10021);
System.out.println(sysUser);
}

selectBatchIds

  • mapper.selectBatchIds 主键查询多个值
1
2
3
4
5
6
7
8
9
/**
* mapper.selectBatchIds 主键查询多个值
* 查询主键中等于 【10001, 10002, 10005】的数据
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE user_id IN ( 10001 , 10002 , 10005 ) AND deleted='1'
*/
@Test
void selectBatchIds() {
mapper.selectBatchIds(Arrays.asList(10001, 10002, 10005)).forEach(System.out::println);
}

selectByMap

  • mapper.selectByMap 根据 map 对象查询
  • Map 只能做等值判断,逻辑判断需要使用 Wrapper
1
2
3
4
5
6
7
8
9
10
11
12
/**
* mapper.selectByMap 根据 map 对象查询
* 查询 user_id = 10003的数据
* Map 只能做等值判断,逻辑判断需要使用 Wrapper
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE user_id = 10003 AND deleted='1'
*/
@Test
void selectbyMap() {
Map<String, Object> map = new HashMap<>();
map.put("user_id", 10003);
mapper.selectByMap(map).forEach(System.out::println);
}

selectMaps

  • selectMaps 返回wrapper的结果集
1
2
3
4
5
6
7
8
9
10
11
/**
* selectMaps 返回wrapper的结果集
* selectMaps 返回的是一个结果集,selectList 返回的是对象列表
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_id = 10001)
*/
@Test
void selectMaps() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("user_id", 10001);
mapper.selectMaps(wrapper).forEach(System.out::println);
}

分页查询

配置

  • 需要先注入分页的 bean,然后才能使用分页查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.adalucky.MybatisPlus.config;

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

/**
* @ClassName MyBatisPlusConfig
* @Description MyBatis-Plus 配置类
* @Author ada
* @Computer Mac mini
* @Date 2022/1/28 18:25
* @JDKVersion JDK1.8
*/
@Configuration
public class MyBatisPlusConfig {
// 最新版 分页插件 DbType.MYSQL 指定为自己的方言
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptorPage() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

selectPage

  • selectPage 分页查询,返回的结果集是一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* selectPage 分页查询,返回的结果集是一个对象
* getSize() 每页的大小和参数 size 一样
* getTotal() 统计查询的总条数
* getRecords() 分页后的结果列表
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' LIMIT 5,5
*/
@Test
void selectPage(){
IPage<SysUser> ipage = new Page<>(2,5);
IPage<SysUser> result = mapper.selectPage(ipage, null);
System.out.println(result.getSize());
System.out.println(result.getTotal());
result.getRecords().forEach(System.out::println);
}

selectMapsPage

  • selectMapsPage 分页查询,返回的结果集是一个 map
1
2
3
4
5
6
7
8
9
10
/**
* selectMapsPage 分页查询,返回的结果集是一个 map
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' LIMIT 5,5
*/
@Test
void selectMapsPage() {
IPage<Map<String, Object>> ipage = new Page<>(2,5);
IPage<Map<String, Object>> mapIPage = mapper.selectMapsPage(ipage, null);
mapIPage.getRecords().forEach(System.out::println);
}

selectObjs

  • selectObjs() 查询主键的集合,返回的是所有主键的列表
1
2
3
4
5
6
7
8
9
10
/**
* selectObjs() 查询主键的集合,返回的是所有主键的列表
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1'
*/
@Test
void selectObjs(){
List<Object> objects = mapper.selectObjs(null);
objects.forEach(System.out::println);

}

selectOne

  • selectOne() 要求返回的结果中只能有一条数据
1
2
3
4
5
6
7
8
9
10
11
12
/**
* selectOne() 要求返回的结果中只能有一条数据
* 需要通过 wrapper 构造一个唯一结果
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE deleted='1' AND (user_id = 10003)
*/
@Test
void selectOne() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("user_id", 10003);
SysUser sysUser = mapper.selectOne(wrapper);
System.out.println(sysUser);
}

delete

deleteById

  • deleteById 根据主键删除
1
2
3
4
5
6
7
8
9
/**
* deleteById 根据主键删除
* 根据主键 id 删除,因为配了逻辑删除,这里只会修改删除标识,不会物理删除
* Execute SQL:UPDATE sys_user SET deleted='0' WHERE user_id=100 AND deleted='1'
*/
@Test
void deleteById() {
mapper.deleteById(100);
}

deleteBatchIds

  • deleteBatchIds 删除 ID 集合的数据
1
2
3
4
5
6
7
8
9
10
/**
* deleteBatchIds 删除 ID 集合的数据
* 传入的是一个主键的集合,多个 id,返回的结果集是一个 int 类型,删除成功了几条
* Execute SQL:UPDATE sys_user SET deleted='0' WHERE user_id IN ( 10001 , 10002 , 10003 ) AND deleted='1'
*/
@Test
void deleteBatchIds(){
int i = mapper.deleteBatchIds(Arrays.asList(10001, 10002, 10003));
System.out.println(i);
}

delete

  • deleted 删除数据(如果没有条件就是删除所有的
1
2
3
4
5
6
7
8
9
10
11
12
/**
* deleted 删除数据
* 传递的参数是一个 wrapper 条件构造器(如果没有条件就是删除所有的)
* Execute SQL:UPDATE sys_user SET deleted='0' WHERE deleted='1' AND (user_name = '测试4')
*/
@Test
void deleted() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("user_name", "测试4");
int delete = mapper.delete(wrapper);
System.out.println(delete);
}

deleteByMap

  • deleteByMap 删除 Map 中的等值数据
1
2
3
4
5
6
7
8
9
10
11
12
/**
* deleteByMap 删除 Map 中的等值数据
* 做的等值判断,如下示例就是判断数据库中 user_id=10006 的数据,返回的是一个 int 类型删除成功的行数
* Execute SQL:UPDATE sys_user SET deleted='0' WHERE user_id = 10006 AND deleted='1'
*/
@Test
void deleteByMap(){
Map<String, Object> map = new HashMap<>();
map.put("user_id", 10006);
int i = mapper.deleteByMap(map);
System.out.println(i);
}

update

updateById

  • updateById 根据主键 ID 去更新数据
1
2
3
4
5
6
7
8
9
10
11
12
/**
* updateById 根据主键 ID 去更新数据
* 这里是先查了一个数据出来,然后 set 替换值后再把这个对象去替换
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE user_id=10021 AND deleted='1'
* Execute SQL:UPDATE sys_user SET dept_id=105, login_name='13512648422', user_name='update更新', user_type='00', email='', phonenumber='', sex=2, avatar='', status=1, login_ip='', update_time='2022-03-08T16:48:32.042', version=3 WHERE user_id=10021 AND version=2 AND deleted='1'
*/
@Test
void updateById(){
SysUser sysUser = mapper.selectById(10021);
sysUser.setUserName("update更新");
mapper.updateById(sysUser);
}

update

  • update 更新满足条件的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* update 更新满足条件的数据
* 参数为一个实体对象,一个条件构造器
* 本示例为更新 10021 这个数据的名称为 update 同时该数据的 status需要为启用状态才会更新
* Execute SQL:SELECT user_id,dept_id,login_name,user_name,user_type,email,phonenumber,sex,avatar,status,deleted,project_id,login_ip,login_date,version FROM sys_user WHERE user_id=10021 AND deleted='1'
* Execute SQL:UPDATE sys_user SET dept_id=105, login_name='13512648422', user_name='update', user_type='00', email='', phonenumber='', sex=2, avatar='', status=1, login_ip='', update_time='2022-03-08T17:05:47.010', version=5 WHERE deleted='1' AND (status = 0 AND version = 4)
*/
@Test
void update() {
SysUser sysUser = mapper.selectById(10021);
sysUser.setUserName("update");
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("status", StatusEnums.ENABLE);
int update = mapper.update(sysUser, wrapper);
System.out.println(update);
}

自定义 SQL

多表关联查询

  • 需求:用户有自己的属性 姓名 账户 所属部门,所属公司 我们需要通过关联查询出某一个用户的部门和个人信息账号等
  • 说明:这里只用了两张表,和4 个字段,多张表和多个字段实现方法一样,注意 VO 中的成员属性需要和数据库中的字段名一致,如果不一致需要在 sql 语句中给查询的字段起别名保持和 VO 中的一致,不然无法对应会为 null
  • VO: view object 数据库返回给视图的叫做(返回给页面展示的) VO
  • DTO:Data Transfer Object 业务层传输给数据库的叫做(前端传递的参数) DTO
1
SELECT u.login_name,u.user_id,u.user_name,d.dept_name FROM sys_dept d,sys_user u WHERE u.dept_id=d.dept_id and u.user_id=10003
  • 新建一个 VO 实体,该实体定义接收的数据字段
1
2
3
4
5
6
7
@Data
public class UserVO {
private Integer userId;
private String loginName;
private String userName;
private String deptName;
}
  • Mapper 中自己实现方法(自带的方法已经满足不了我们的需求了)
  • 方法命名建议也采用 select _ By _ 的方式(service 中用 get)
  • 通过注解 Select 把 sql 放进去,sql 的参数用 #{ 参数 } 引用 这里面的参数应该是按照顺序取的,第一个#{}对应着方法中的第一个参数,第二个对应着方法中的第二个参数
  • 方法中返回的是 List<VO> 对象,传递的参数就是上面 sql 引用的参数
1
2
3
4
5
6
7
8
9
10
11
12
@Component
public interface SysUserMapper extends BaseMapper<SysUser> {
/**
*
* @param user_id 用户表的 id 主键
* @param status 是否禁用标识,用的枚举类
* @return 查询结果列表封装到 UserVO 中
*/
//@Select("SELECT u.login_name,u.user_id,u.user_name,d.dept_name FROM sys_dept d,sys_user u WHERE u.dept_id=d.dept_id and u.user_id=#{user_id}")
@Select("SELECT u.login_name,u.user_id,u.user_name,d.dept_name FROM sys_dept d,sys_user u WHERE u.dept_id=d.dept_id AND u.user_id=#{user_id} AND u.status=#{status}")
List<UserVO> selectUserVoById(Integer user_id, StatusEnums status);
}
  • 调用 selectUserVoById
1
2
3
4
5
6
7
8
9
10
/**
* selectUserVoById 自定义 sql
* 需要传递两个参数,一个是用户ID,一个是启用状态(这里直接用的枚举中的)
* Execute SQL:SELECT u.login_name,u.user_id,u.user_name,d.dept_name FROM sys_dept d,sys_user u WHERE u.dept_id=d.dept_id AND u.user_id=10003 AND u.status=1
*/
@Test
void selectUserVoById(){
List<UserVO> userVOS = mapper.selectUserVoById(10003, StatusEnums.ENABLE);
userVOS.forEach(System.out::println);
}

SQL日志打印

  • 在执行程序的时候如果需要查看执行的 sql 语句我们可以通过配置打印出来

mybatis-plus实现类输出

  • 通过配置 yml 文件中的 mybatis-plus进行配置输出日志
1
2
3
4
mybatis-plus:
configuration:
#打印mybatis-plus的 sql 执行语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

mybatis-plus实现类输出

debug输出

  • 正式环境推荐使用该方式,以 debug 模式显示出执行的 sql 语句和参数

debug输出

p6spy实现

  • 建议在单元测试中、或者本地调试使用,因为会消耗性能,不建议部署在线上环境
  • 添加 p6spy 依赖
1
2
3
4
5
6
<!--执行 SQL 分析打印,yml配置中需要修改数据的连接驱动为 driver-class-name: com.p6spy.engine.spy.P6SpyDriver  url 改成 jdbc:p6spy:mysql://127.xxx -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
  • 修改数据库驱动为:com.p6spy.engine.spy.P6SpyDriver
  • url 修改为:jdbc:p6spy:mysql://127.xxx
1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:3306/data_auto_endpoint?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
  • resources 下新增 spy.properties (是在resources 下的根目录新增 不能在 config 中新增,文件名也不要写错了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# logMessageFormat自定义日志打印格式如下
# Consume Time:48 ms 2022-03-07 15:14:51
# Execute SQL: 执行的 sql 语句
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
# 使用日志系统记录sql
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 设置使用p6spy driver来做代理
deregisterdrivers=true
# JDBC URL 是否加上前缀,设置为 true,会加上 p6spy: 作为前缀。取值 true| false
useprefix=true
## 配置记录Log例外 error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 秒
outagedetectioninterval=2