Mybatis Plus 学习笔记

注解

@TableName

表名注解,当数据库中的表名和实体类名之间不能完全匹配时,需要使用这个注解进行绑定。

如:数据库表中的数据库表名为t_student,而实体类的类名为:Student,则需要在该类上增加注解:

1
2
3
4
@TableName(value = "t_student"
public class Student {
private Long stuId;
}
全局设置数据库表前缀

当数据库表中的所有表名前有前缀时,需要每次指定@TableName注解,很麻烦,需要使用全局配置:

1
2
3
4
mybatis-plus: 
global-config:
db-config:
table-prefix: "t_"

@TableId

主键注解,当数据库中的主键和实体类中主键属性不同时,使用这个注解进行绑定。
如:数据库表中的主键是id,而实体类中的属性值是stuId,那么就在实体类主键属性上加上注解

1
2
3
4
public class Student {
@TableId(value = "id")
private Long stuId;
}
IdType

@TableId注解还有一个属性配置是主键的主键类型,具体可选值如下,默认值为:IdType.NONE

描述
AUTO 数据库自增
INPUT 自行输入
ID_WORKER 分布式全局唯一ID 长整型类型
UUID 32位UUID字符串
NONE 无状态(默认
ID_WORKER_STR 分布式全局唯一ID 字符串类型
全局设置设置注解策略

设置主键策略,默认值:ID_WORKER

1
2
3
4
mybatis-plus: 
global-config:
db-config:
id-type: AUTO
全局设置驼峰功能

mybatisPlus 有一个全局的配置策略,可以实现数据库表中的列名字段是下划线分割,自动对应实体类中驼峰名:

如:数据库中的stu_address字段与实体类中的stuAddress属性字段相对应。

1
2
3
4
5
mybatis-plus: 
global-config:
db-config:
table-underline: true
# 表名、是否使用下划线命名(默认 true:默认数据库表下划线命名)

在 Springboot 中,可以通过设置map-underscore-to-camel-case属性为 true 来开启驼峰功能。因此 mybatisPlus 也继承了这种配置,默认为 true

1
2
3
mybatis-plus: 
configuration:
map-underscore-to-camel-case: true

@TableField

value

当显示配置的时候,则按设置的值为准。

exist

标识该字段是数据库表字段,当实体类中有属性字段不在数据库表中的时候,就需要在数据库操作时忽略这些字段,因此可以配置exist=false属性配置,如果不配置,默认为:true

BaseMapper

BaseMapper 接口是专门用来进行通用增删改查的接口,通过指定泛型可以对数据实体对象进行通用的CIUD操作,大致可以分为四类:

Insert

int insert(T entity)

insert 方法会将实体对象中的非空属性值映射到数据库,进行插入操作,也就是说有多少非空属性,执行的SQL语句中才插入多少,和 mybatis 中的 insertSeletive 一样,有值才操作。

亮点

插入成功默认返回主键,mybatis Plus 插入数据之后,实体类中的主键会返回给实体类,相比 mybatis 原生的默认不返回要很多繁琐的配置:

1
2
3
4
<insert id="insert" useGeneratedKeys="true" keyProperty="stuId" parameterType="org.woodwhales.king.Student">
insert into student(userName, password, comment)
values(#{userName}, #{password}, #{comment})
</insert>

其中:keyProperty中配置的是实体类中的属性字段,用来接收数据库表返回的主键值。

Update

根据主键更新

int updateById(T entity)

带条件的更新

int update(T entity, Wrapper<T> updateWrapper)

如:更新姓名为Tom且年龄为 28 的学生数据:

1
2
3
4
5
6
7
8
9
LambdaQueryWrapper<Student> lambdaQueryWrapper = new QueryWrapper<Student>().lambda()
.eq(Student::getName, "Tom")
.eq(Student::getAge, 28);

Student student = Student.builder()
.name("woodwhales")
.age(20)
.email("woodwhales@woodwhales.com").build();
int row = studentMapper.update(student, lambdaQueryWrapper);

SQL脚本执行日志

1
2
3
==> Preparing: UPDATE student SET name=?, age=?, email=? WHERE name = ? AND age = ? 
==> Parameters: woodwhales(String), 20(Integer), woodwhales@woodwhales.com(String), Tom(String), 28(Integer)
<== Updates: 1

Select

Delete

条件构造器

EntityWapper

注意:条件构造器中使用的是数据列名,而不是实体类对象属性。

分页查询(带条件)

对于条件构造器的编写,可以使用最原始写法:

1
2
3
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
queryWrapper.eq(“stu_name”, "Tom");
queryWrapper.like("email", "woodwhale");

也可以使用QueryWrapper<User>().lambda()这样类似于 JDK1.8 的lambda表达式编写条件构造器,下文中均采用此写法。

例:查询数据库中 student 表(Student 实体类)中的所有姓名为Tom且年龄在10-20 岁之间的分页数据,当前页为 1,每页显示数为 2

1
2
3
4
5
6
7
8
9
10
IPage<Student> pager = studentMapper.selectPage(new Page<Student>(1, 2), 
new QueryWrapper<User>().lambda()
.eq(Student::getName, "Tom")
.between(Student::getAge, 10, 20));

List<Student> studentList = pager.getRecords();
studentList.forEach(System.out::println);
System.out.println("total: " + pager.getTotal());
System.out.println("pages: " + pager.getPages());
System.out.println("size: " + pager.getSize());

SQL脚本执行日志

1
2
==> Preparing: SELECT id,name,age,email FROM student WHERE name = ? AND age BETWEEN ? AND ? LIMIT ?,? 
==> Parameters: Tom(String), 10(Integer), 20(Integer), 0(Long), 1(Long)

注意:上面代码查询出来的对象pager中的totalpages总是为 0 ,解决办法:编写一个配置类,自定义一个分页插件PaginationInterceptor配置到 spring 中即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;

@Configuration
public class AppConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor page = new PaginationInterceptor();
page.setDialectType(DbType.MYSQL.getDb()); // 设置数据库方言
return page;
}
}

上述代码中:只需要返回new PaginationInterceptor()即可使得selectPage返回对象中有数据总记录数。

带条件的查询-并且

查询数据库中 student 表(Student 实体类)中的所有邮箱名字含有woodwhale且年龄在10-20 岁之间的所有数据:

1
2
3
4
List<Student> studentList = studentMapper.selectList(new QueryWrapper<Student>()
.lambda()
.like(Student::getEmail, "woodwhale")
.between(Student::getAge, 10, 20));

SQL脚本执行日志

1
2
==> Preparing: SELECT id,name,age,email FROM student WHERE email LIKE ? AND age BETWEEN ? AND ? 
==> Parameters: %woodwhale%(String), 10(Integer), 20(Integer)

带条件的查询-或者

1
2
3
4
5
6
IPage<Student> pager = studentMapper.selectPage(new Page<Student>(1, 2), 
new QueryWrapper<Student>().lambda()
.eq(Student::getName, "Tom")
.between(Student::getAge, 10, 20)
.or()
.like(Student::getEmail, "woodwhale"));

注意or()中可以传参一个布尔表达式,当条件为false时,或者作用失效,后面的条件会与前面的条件形成并且的关系。引用官方的解释:

主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)

SQL脚本执行日志

1
2
3
4
==>  Preparing: SELECT COUNT(1) FROM student WHERE name = ? AND age BETWEEN ? AND ? OR email LIKE ? 
==> Parameters: Tom(String), 10(Integer), 20(Integer), %woodwhale%(String)
==> Preparing: SELECT id,name,age,email FROM student WHERE name = ? AND age BETWEEN ? AND ? OR email LIKE ? limit ?
==> Parameters: Tom(String), 10(Integer), 20(Integer), %woodwhale%(String), 1(Long)

查询结果排序

orderBy(boolean condition, boolean isAsc, R... columns)

对查询的结果进行降序排列(isAsc 设置成了false

1
2
List<Student> studentList = userMapper.selectList(Wrappers.<Student>query().orderBy(true, false, "age"));
studentList.forEach(System.out::println);

SQL 脚本执行日志

1
2
3
==> Preparing: SELECT id,name,age,email FROM student ORDER BY age DESC 
==> Parameters:
<== Total: 8

mybatis Plus 提供了更加便捷的方法:

降序排序

orderByDesc(R column)

orderByDesc(R... columns)

orderByDesc(boolean condition, R... columns)

升序排序

orderByAsc(R column)

orderByAsc(R... columns)

orderByAsc(boolean condition, R... columns)

领域模式

ActiveRecord

必须存在对应的原始mapper并继承baseMapper并且可以使用的前提下,才能使用此 AR 模式。

使用方法

实体类对象继承 Model抽象类即可。

1
2
3
4
5
6
7
8
9
@Data
@Builder
public class Student extends Model<Student>{
@TableId(type=IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}

对数据的增删改查操作,直接操作自己即可。

1
2
Student student = Student.builder().id(null).name("adc").age(20).email("ss@q.com").build();
boolean insert = student.insert();

条件查询

领域模式下查询主键的方法,有参数传入时以传入为准,无参数时以对象为准。当对象没有主键时,会报com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: selectById primaryKey is null.异常,删除主键的方法同理,另外删除逻辑上不存在的数据也是返回成功。

1
2
Student student = Student.builder().id(5L).build();
Student result = student.selectById(6L);

SQL脚本执行日志

1
2
3
==> Preparing: SELECT id,name,age,email FROM student WHERE id=? 
==> Parameters: 6(Long)
<== Total: 1

分页查询

分页返回的数据对象是IPage,从IPage可以获取相应的总记录数,记录,当前页,每页数。

1
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)

代码生成器

AutoGenerator 是 MyBatis-Plus 的代码生成器,MyBatis-Plus 的代码生成器比 Mybatis 原生的代码生成器强大的地方在于,能够快速生成通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

以下代码生成器中开启了lomboksprigboot-webswagger2freemarker,因此需要添加依赖:

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
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- spring-boot-web -->
<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.1.0</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!-- swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>

最佳实践

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
import java.util.ArrayList;
import java.util.List;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.FileOutConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.TemplateConfig;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

public class Generator {
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();

// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setActiveRecord(true); // 开启领域模式
gc.setIdType(IdType.AUTO);
gc.setServiceName("%sService"); // 各层文件名称方式,例如: %sAction 生成 UserAction
gc.setOutputDir(projectPath + "/src/main/java"); // 生成文件的输出目录【默认 D 盘根目录】
gc.setAuthor("woodwhales"); // [开发者]
gc.setFileOverride(true); // 开启文件覆盖
gc.setOpen(false); // 是否打开输出目录
gc.setSwagger2(true); // 开启 swagger2 模式
gc.setBaseResultMap(true); // 开启 BaseResultMap
gc.setBaseColumnList(true); // 开启 baseColumnList
mpg.setGlobalConfig(gc);

// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mytest?useUnicode=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
dsc.setDriverName("com.mysql.cj.jdbc.Driver"); // 注意:mysql 5.7 之后的驱动是com.mysql.cj.jdbc.Driver
dsc.setDbType(DbType.MYSQL);
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);

// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent("org.woodwahles"); // [实体类所在模块的父模块名]
pc.setModuleName("king"); // [实体类所在模块名]
pc.setXml("mapper"); // 默认包名:mapper.xml
mpg.setPackageInfo(pc);

// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
List<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义mapper文件名称
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName() + "/"
+ tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}

});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
mpg.setTemplate(new TemplateConfig().setXml(null));

// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel); // 下划线转驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setInclude("student"); // [表名]
strategy.setSuperEntityColumns("id");
strategy.setControllerMappingHyphenStyle(true);
// strategy.setTablePrefix(pc.getModuleName() + "_"); // 数据库表名前缀
mpg.setStrategy(strategy);
// 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}

生成的项目结构如下:

代码生成器生成的项目结构

插件

攻击 SQL 阻断解析器

作用!阻止恶意的全表更新删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class AppConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor page = new PaginationInterceptor();
page.setDialectType(DbType.MYSQL.getDb()); // 设置数据库方言

List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser());
page.setSqlParserList(sqlParserList);

return page;
}
}

性能分析插件

该插件只用于开发环境,不建议生产环境使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class AppConfig {
/**
* 性能分析插件
*/
@Bean
@Profile({"dev","test"}) // 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
// maxTime 指的是 SQL 执行最大时长,超过自动停止运行,有助于发现问题。
performanceInterceptor.setMaxTime(100);
// SQL 是否格式化
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
}

乐观锁插件

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

乐观锁的实现思路:

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

插件使用步骤:

  1. 将插件注入spring 框架:
1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
  1. 使用注解@Version注解实体字段(必要步骤)
1
2
@Version
private Integer version;

特别说明

  • 支持的数据类型只有:intIntegerlongLongDateTimestampLocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper)方法下, wrapper不能复用!

逻辑删除插件

首先指定逻辑删除字段在数据库表中的取值:

1
2
3
4
5
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

将插件注入spring中:

1
2
3
4
5
6
7
8
9
10
11
12
13
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfiguration {

@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}

实体类字段上加上@TableLogic注解

1
2
@TableLogic
private Integer deleted;

使用 Mybatis-Plus 自带方法删除和查找都会附带逻辑删除功能(自己写的xml不会)

MybatisX插件(IDEA插件)

MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。

安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。

插件功能:Java 与 XML 调回跳转,Mapper 方法自动生成 XML。

插件源码:https://gitee.com/baomidou/MybatisX。

updated updated 2020-03-02 2020-03-02
本文结束感谢阅读

本文标题:Mybatis Plus 学习笔记

本文作者:木鲸鱼

微信公号:木鲸鱼 | woodwhales

原始链接:https://woodwhales.cn/2019/03/28/025/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%