Mybatis Plus
# 依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.6</version>
</parent>
<dependencies>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- spring整合test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- mybatis-plus的驱动包 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
<!-- mysql要选择正确版本的驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
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
# CRUD操作API
内置通用 BaseMapper,少量配置即可实现单表CRUD 操作(如果只做单表增删查改不需要你写任何的sql)
| 功能 | MP接口 |
|---|---|
| 新增 | int insert(T t) |
| 删除 | int deleteById(Serializable id) |
| 批量删除 | int deleteBatchIds(Collection idList) |
| 修改 | int updateById(T t) |
| 根据Id查询 | T selectById(Serializable id) |
| 查询全部 | List<T> selectList() |
| 分页查询 | IPage<T> selectPage(IPage<T> page) |
| 按条件查询 | IPage<T> selectPage(Wrapper<T> queryWrapper) |
# 分页查询IPage使用示例
使用mp的分页功能必须存在一个配置类,配置分页拦截器
/**
* 如果需要使用到mybatis-plus的分页功能,必须存在一个配置类
* 该配置类创建Mybatis的拦截器,这个拦截器的作用就是在你执行selectPage的方法的时候
* 对sql进行拦截,然后拼接limit语句实现分页。
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor createMybatisPlusInterceptor(){
//1. 创建Mybatisplus拦截器
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//2. 往拦截器中添加分页拦截器
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//3 .返回
return mybatisPlusInterceptor;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
测试
/**
* 分页查询
*/
@Test
public void testPage(){
//1. 设置当前页与页面大小
Page<User> page =new Page<>(1,2); //当前页1 页面大小是2
//2. 创建分页需要条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//3. 调用selectPage方法实现分页,分页的结果会被封装到Page对象中
userMapper.selectPage(page,queryWrapper);
System.out.println("页面大小:"+page.getSize());
System.out.println("页面数据:"+page.getRecords());
System.out.println("当前页:"+page.getCurrent());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
若是自定义方法也想使用mp的分页功能需要满足以下几点:
- 如果自己实现的sql语句需要使用分页功能,那么该方法必须接收一个Page对象。
- 方法的返回值必须也是一个Page对象
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
public interface UserMapper extends BaseMapper<User> {
@Select("select * from user where age>#{age}")
public Page<User> selectByAge(Page<User> page, @Param("age") Integer age);
}
2
3
4
5
6
7
当SQL语句非常复杂的时候,比如需要分组、条件、排序,使用mp就需要调用较多的api,并且不可复用,这里就可以自己通过xml实现sql语句来进行复用。
# 按条件查询
QueryMrapper 和 LambdaQueryWrapper 的区别
**相同点:**设置查询条件、查询字段等信息
不同点:
- QueryMrapper 需要设置字段名字字符串的形式编写代码,功能强大
优点:
- 给查询的字段起别名
- 可以执行聚合函数
- LambdaQueryWrapper 通过lambda表达式写字段名字,有智能提示
查询年龄大于18岁的用户
@Test
void testQueryWrapper() {
//1. 创建查询条件封装对象,可以指定泛型
QueryWrapper<User> wrapper = new QueryWrapper();
//2.字段age大于18
wrapper.gt("age", 18);
//3.执行查询
List<User> users = userMapper.selectList(wrapper);
//4.输出结果
users.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
# lambda格式按条件查询
查询年龄小于10的用户
@Test
void testLambdaQueryWrapper() {
//1.创建lambda查询包装器,支持泛型
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper();
//2. 使用lambda参数,相当于调用 user -> user.getAge()方法,获取列名
wrapper.le(User::getAge, 10);
//3.查询
List<User> users = userMapper.selectList(wrapper);
//4.输出结果
users.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
# 并且关系(and)
查询年龄小于30岁,而且大于10岁的用户
@Test
void testAnd() {
//并且关系
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//支持链式写法
wrapper.lt(User::getAge, 30).gt(User::getAge, 10);
List<User> userList = userMapper.selectList(wrapper);
System.out.println(userList);
}
2
3
4
5
6
7
8
9
生成的SQL语句:
SELECT id,name,gender,password,age,tel FROM user WHERE (age < ? AND age > ?)
# 或者关系(or)
查询年龄小于10岁或者大于30岁的用户
@Test
void testOr() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//或者关系:小于10岁或者大于30岁
wrapper.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userMapper.selectList(wrapper);
System.out.println(userList);
}
2
3
4
5
6
7
8
生成的SQL语句:
SELECT id,name,gender,password,age,tel FROM user WHERE (age < ? OR age > ?)
# if语句控制条件追加
- 如果最小年龄不为空,则查询大于这个年龄的用户
- 如果最大年龄不为空,则查询小于这个年龄的用户
@Test
void testCondition() {
Integer minAge=10; //将来有用户传递进来,此处简化成直接定义变量了
Integer maxAge=null; //将来有用户传递进来,此处简化成直接定义变量了
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//参数1:如果表达式为true,那么查询才使用该条件,也支持链式编程
wrapper.gt(minAge != null, User::getAge, minAge);
wrapper.lt(maxAge != null, User::getAge, maxAge);
//查询
List<User> userList = userMapper.selectList(wrapper);
//输出
userList.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
13
# 查询结果包含模型类中部分属性
查询所有用户,只显示id, name, age三个属性,不是全部列。
使用select(列名...)方法,查询的结果如果封装成实体类,则只有这三个属性有值,其它属性为NULL
@Test
void testSameColumn() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//查询所有用户,只显示id, name, age三个属性,不是全部列
wrapper.select(User::getId, User::getName, User::getAge);
List<User> userList = userMapper.selectList(wrapper);
System.out.println(userList);
}
2
3
4
5
6
7
8
生成的SQL语句:
**SELECT id,name,age FROM user**
# 查询结果包含模型类中未定义的属性
如果查询结果包含模型类中未定义的属性,可以将每个元素封装成Map对象。一行数据代表一个map集合对象,表字段为key,字段内容为value。
**需求:**按性别进行分组,统计每组的人数。只显示统计的人数和性别这两个字段
使用QueryWrapper包装对象的select方法
@Test
void testCountGender() {
//使用QueryWrapper包装对象
QueryWrapper<User> wrapper = new QueryWrapper<>();
//查询2列:人数, 性别。 将聚合函数定义别名做为Map中的键
wrapper.select("count(*) as count, gender");
//按sex分组
wrapper.groupBy("gender");
//这里的查询方法使用selectMaps
List<Map<String, Object>> list = userMapper.selectMaps(wrapper);
list.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
# 查询条件
- 购物设定价格区间、户籍设定年龄区间(le ge匹配 或 between匹配)
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
//范围查询 lt le gt ge eq between
//2. 使用lambda参数,相当于调用 user -> user.getAge()方法,获取列名
// le 小于等于
// lt 小于
// gt 大于
// ge 大于等于
// eq 等于
wrapper.between(User::getAge, 10, 30);
List<User> userList = userMapper.selectList(wrapper);
System.out.println(userList);
2
3
4
5
6
7
8
9
10
11
- 查信息,搜索新闻(非全文检索版:like匹配)
/**
* 需求: 查询姓张的用户
* select * from user where name like '张%'
*/
@Test
public void testFindByLike(){
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// Wrapper.likeRight(字段,值) 模糊查询生成格式:'值%'
// Wrapper.likeLeft(字段,值) 模糊查询生成格式:'%值'
// Wrapper.like(字段,值) 模糊查询生成格式:'%值%'
lambdaQueryWrapper.likeRight(User::getName,"张");
List<User> userList = userMapper.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 统计报表(分组查询聚合函数)
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("gender", "count(*) as nums");
qw.groupBy("gender");
List<Map<String, Object>> maps = userMapper.selectMaps(qw);
System.out.println(maps);
2
3
4
5
# 排序和limit
题目:显示年龄最大的5个用户
- 说明:
①:提示:对年龄进行降序排序
②:仅获取前5条数据(提示:使用分页功能控制数据显示数量) - last()方法的说明:
无视优化规则直接拼接到 sql 的最后(有sql注入的风险,请谨慎使用),注意只能调用一次,多次调用以最后一次为准
/**
* 需求: 查询年龄大于18岁的前三位
* select * from user where age>18 order by age desc limit 3;
*
*/
@Test
public void testFindByLimit(){
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// wrapper.orderByDesc(字段), 按照指定的字段降序排序
// wrapper.orderByAsc(字段), 按照指定的字段升序排序
// wrapper.last("sql片段"), 用于拼接limit关键字的sql片段,将这个片段放在所有sql语句的最后面。
lambdaQueryWrapper.gt(User::getAge,18).orderByDesc(User::getAge).last("limit 3");
List<User> userList = userMapper.selectList(lambdaQueryWrapper);
userList.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
生成的SQL
**SELECT id,name,gender,password,age,tel FROM user WHERE (age > ?) ORDER BY age DESC limit 3**
# 乐观锁(update)
数据库中悲观锁的实现是通过 FOR UPDATE关键字来开启行锁,锁住当前行数据
如:
-- 手动开启事务
START TRANSACTION
SELECT * FROM tbl_user WHERE id=1 FOR UPDATE -- 开启行锁
-- 增、删、改的事情
UPDATE tbl_user SET age=age+1 WHERE id=1
COMMIT/ROLLBACK
2
3
4
5
6
数据库的乐观锁的实现则是简单粗暴,给表添加字段version,控制字段版本,每进行一次修改,version+1
假设当前数据库tbl_user表状态为 age = 12, version =0;
线程A:update tbl_user set age=age+1,version=version+1 where id=1 and version=1
若A先成功,age=13,version=1
线程B:update tbl_user set age=age+1,version=version+1 where id=1 and version=1
由于此时最新的version=2,所以没有符合条件的数据,sql执行失败
**sql语句执行过程:**编译sql语句=>执行修改sql=>根据条件查询数据=>修改数据=>提交事务=>更新到磁盘
**注意:**数据库在执行sql语句的时候是原子操作
mp实现乐观锁的步骤:
- 数据库的表必须添加version字段
- 实体类也得有version属性,并且加上@Version注解表示该属性为版本更新字段
- 添加乐观锁的拦截器
- 执行update语句的时候记得要先把待修改记录的version查出来
配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testUpdateTwo() {
// 先通过要修改的数据id将当前数据以及version查询出来
User user1 = userMapper.selectById(2L); //version=2
User user2 = userMapper.selectById(2L); //version=2
user1.setName("Jack aaa");
userMapper.updateById(user1); //version=>3
user2.setName("Jack bbb");
userMapper.updateById(user2); //verion=2 更新失败
}
2
3
4
5
6
7
8
9
10

# 注解配置
| 注解 | 属性 | 说明 |
|---|---|---|
| @TableField | value:设置字段映射关系,起别名 exist:false 设置不查询该字段 select:false 设置该字段不参与查询,但是增删改参与 | 建立实体类属性名与表字段名的映射关系;在生成sql语句的时候,会根value属性查找到表字段设置与实体类属性名相同的别名 |
| @TableName | value:设置表别名 | 建立实体类名与表名的映射关系 |
| @TableId | type:设置主键属性的生成策略,值参照IdType枚举值 - AUTO(0):使用数据库的id自增策略 - NONE(1):不设置id生成策略 - INPUT(2):用户手动输入id - ASSIGN_ID(3):雪花算法生成id【默认】【推荐】 - ASSIGN_UUID(4):以uuid算法生成id | 设置主键id生成策略 |
| @TableLogic | value:未删除时的值 delval:删除了的值 | 逻辑删除字段,标记当前记录是否被删除,当删除记录时不会真的删除该记录,而是更新该字段的值。 |
# YML全局配置
mybatis-plus:
global-config:
db-config:
# 设置全局id生成策略
id-type: assign_id
# 设置全局表名映射
table-prefix: tbl_
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
2
3
4
5
6
7
8
9
10
11
12
13
# 逆向工程
package cn.kk;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
String moduleName = scanner("模块名");
gc.setOutputDir(projectPath + "/"+moduleName+"/src/main/java");
//代码的作者
gc.setAuthor("kk");
gc.setOpen(false);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/db2?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("功能模块名"));
//设置父级包名
pc.setParent("cn.kk");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/"+moduleName+"/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
// strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
// strategy.setSuperEntityColumns("id");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
String preName = scanner("请输入表前缀名");
strategy.setTablePrefix(preName); // 设置表前缀
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
// 执行
mpg.execute();
}
}
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
111
112
113
114
115
116
117
118