MyBatis-PLus
MyBatis-PLus
飞书链接:https://b11et3un53m.feishu.cn/wiki/PsyawI04ei2FQykqfcPcmd7Dnsc
一 入门
1.1 demo
引入依赖
1
2
3
4
5<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>Mapper 继承BaseMapper
测试
1.2 常见注解
MP通过扫描实体类,并基于反射获取实体类信息作为数据库表信息
- 类名驼峰转下划线作为表名
- 名为id的字段为主键
- 变量名驼峰转下划线作为表的字段名
@TableName
:表名注解,标识实体类对应的表```java
@TableName(“tb_user”)
public class User {private Long id; private String name;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- 两种属性
![image-20231018204639104](https://myl-mdimg.oss-cn-beijing.aliyuncs.com/TyporaImg/MybatisPlus.assets/image-20231018204639104.png)
- `@TableId`:主键注解,标识实体类中的主键字段
- ```java
@TableName("user")
public class User {
@TableId("id")
private Long id;
private String name;
}常见三种属性
@TableField
:普通字段注解- ```java
@TableName(“user”)
public class User {
}@TableId private Long id; private String name; private Integer age; @TableField("isMarried") private Boolean isMarried; @TableField("concat") private String concat;
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
- 一般情况下我们并不需要给字段添加`@TableField`注解,一些特殊情况除外:
- 成员变量名与数据库字段名不一致
- 成员变量是以`isXXX`命名,按照`JavaBean`的规范,`MybatisPlus`识别字段时会把`is`去除,这就导致与数据库不符。
- 成员变量名与数据库一致,但是与数据库的关键字冲突。使用`@TableField`注解给字段名添加````转义
### 1.3 常见配置
- yaml配置文件
- 注意:注解的配置优先级别下面这个全局的优先级高
- ```yaml
mybatis-plus:
# 实体类的别名扫描包
type-aliases-package: com.itheima.mp.domain.po
# Mapper.xml文件地址,当前这个是默认值。
mapper-locations: "classpath*:/mapper/**/*.xml"
# 全局id类型为自增长
global-config:
db-config:
id-type: auto #assigh_id 雪花算法
- ```java
二 核心功能
2.1 条件构造器
- 除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以
id
作为where
条件以外,还支持更加复杂的where
条件。 Wrapper
为条件构造器的抽象类AbstractWrapper
提供了where中包含的所有条件构造方法QueryWrapper
在AbstractWrapper
的基础上拓展了一个select方法,允许指定查询字段UpdateWrapper
在AbstractWrapper
的基础上拓展了一个set方法,允许指定SQL中的SET部分
2.1.1 QueryWrapper
- ```java
@Test
void testQueryWrapper() {
}// 1.构建查询条件 where name like "%o%" AND balance >= 1000 QueryWrapper<User> wrapper = new QueryWrapper<User>() .select("id", "username", "info", "balance") .like("username", "o") .ge("balance", 1000); // 2.查询数据 List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
11
- ```Java
@Test
void testUpdateByQueryWrapper() {
// 1.构建查询条件 where name = "Jack"
QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");
// 2.更新数据,user中非null字段都会作为set语句
User user = new User();
user.setBalance(2000);
userMapper.update(user, wrapper);
}
2.1.2 UpdateWrapper
- ```Java
@Test
void testUpdateWrapper() {
}List<Long> ids = List.of(1L, 2L, 4L); // 1.生成SQL UpdateWrapper<User> wrapper = new UpdateWrapper<User>() .setSql("balance = balance - 200") // SET balance = balance - 200 .in("id", ids); // WHERE id in (1, 2, 4) // 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据, // 而是基于UpdateWrapper中的setSQL来更新 userMapper.update(null, wrapper);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#### 2.1.3 LambdaQueryWrapper
- 无论是`QueryWrapper`还是`UpdateWrapper`在构造条件的时候都需要写死字段名称,会出现字符串`魔法值`。这在编程规范中显然是不推荐的。
- 其中一种办法是基于变量的`gettter`方法结合反射技术。因此我们只要将条件对应的字段的`getter`方法传递给MybatisPlus,它就能计算出对应的变量名了
- `LambdaQueryWrapper`和`LambdaUpdateWrapper`
- ```java
@Test
void testLambdaQueryWrapper() {
// 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.lambda()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
2.2 自定义SQL
2.2.1 基本用法
-
这种写法在某些企业是不允许的,因为SQL语句最好都维护在持久层,而不是业务层
MybatisPlus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合Mapper.xml编写SQL
```java
@Test
void testCustomWrapper() {// 1.准备自定义查询条件 List<Long> ids = List.of(1L, 2L, 4L); QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids); // 2.调用mapper的自定义方法,直接传递Wrapper userMapper.deductBalanceByIds(200, wrapper);
}
1
2
3
4
5
6
7
8
- 然后在UserMapper中自定义SQL
```java
public interface UserMapper extends BaseMapper<User> {
@Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}
2.2.2 多表关联
利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。
基于mybtis
1
2
3
4
5
6
7
8
9
10<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user u
INNER JOIN address a ON u.id = a.user_id
WHERE u.id
<foreach collection="ids" separator="," item="id" open="IN (" close=")">
#{id}
</foreach>
AND a.city = #{city}
</select>使用wrapper
1
2
3
4
5
6
7
8
9
10
11
12
void testCustomJoinWrapper() {
// 1.准备自定义查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("u.id", List.of(1L, 2L, 4L))
.eq("a.city", "北京");
// 2.调用mapper的自定义方法
List<User> users = userMapper.queryUserByWrapper(wrapper);
users.forEach(System.out::println);
}
2.3 Service接口
- MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。
- 通用接口为
IService
默认实现为ServiceImpl
,其中封装的方法可以分为以下几类:save
:新增remove
:删除update
:更新get
:查询单个结果list
:查询集合结果count
:计数page
:分页查询
2.3.1 基本用法
由于
Service
中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService
,而是自定义Service
接口,然后继承IService
以拓展方法。同时,让自定义的Service实现类
继承ServiceImpl
,这样就不用自己实现IService
中的接口了。首先,定义
IUserService
,继承IService
:1
2
3public interface IUserService extends IService<User> {
// 拓展自定义方法
}然后,编写
UserServiceImpl
类,继承ServiceImpl
,实现UserService
:1
2
3
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
2.3.2 Lambda
IService
中还提供了Lambda功能来简化我们的复杂查询及更新功能原来UserController中的条件查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List<UserVO> queryUsers(UserQuery query){
// 1.组织条件
String username = query.getName();
Integer status = query.getStatus();
Integer minBalance = query.getMinBalance();
Integer maxBalance = query.getMaxBalance();
LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda()
.like(username != null, User::getUsername, username)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance);
// 2.查询用户
List<User> users = userService.list(wrapper);
// 3.处理vo
return BeanUtil.copyToList(users, UserVO.class);
}Service中对
LambdaQueryWrapper
和LambdaUpdateWrapper
的用法进一步做了简化。我们无需自己通过new
的方式来创建Wrapper
,而是直接调用lambdaQuery
和lambdaUpdate
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public List<UserVO> queryUsers(UserQuery query){
// 1.组织条件
String username = query.getName();
Integer status = query.getStatus();
Integer minBalance = query.getMinBalance();
Integer maxBalance = query.getMaxBalance();
// 2.查询用户
List<User> users = userService.lambdaQuery()
.like(username != null, User::getUsername, username)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
//最后的.one()返回最多一个结果,.list()返回集合, .count()返回计数结果
// 3.处理vo
return BeanUtil.copyToList(users, UserVO.class);
}需求新增:如果扣减后余额为0,则将用户status修改为冻结状态(2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(id);
// 2.校验用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
// 3.校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
// 4.扣减余额 update tb_user set balance = balance - ?
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBalance) // 更新余额
.set(remainBalance == 0, User::getStatus, 2) // 动态判断,是否更新status
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance()) // 乐观锁
.update();
}
2.3.4 批量新增
比for循环一个一个插入快很多
MybatisPlus
的批处理是基于PrepareStatement
的预编译模式,然后批量提交,最终在数据库执行时还是会有多条insert语句,逐条插入数据。修改项目中的application.yml文件,在jdbc的url后面添加参数
&rewriteBatchedStatements=true
:1
2
3
4
5
6spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: MySQL123性能将进一步提升
三 扩展功能
3.1 代码生成器
- idea安装
MyBatisPlus
插件,按步骤填写参数即可
3.2 静态工具
- 静态工具类
Db
- 一些静态方法与
Iservice
中的方法签名基本一致
- 一些静态方法与
3.3 逻辑删除
删除的时候并没有真正的删除
步骤
在实体类中添加一个
deleted
字段在
application.yml
中配置逻辑删除字段:1
2
3
4
5
6mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)执行删除操作
1
2
3
4
5
void testDeleteByLogic() {
// 删除方法与以前没有区别
addressService.removeById(59L);
}
3.4 通用枚举
我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是
int
类型,对应的PO也是Integer
。因此业务操作时必须手动把枚举
与Integer
转换,非常麻烦。解决
定义枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum UserStatus {
NORMAL(1, "正常"),
FREEZE(2, "冻结");
private final int value;
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}使用
@EnumValue
标记枚举属性的值配置枚举处理器(在yaml文件中)
1
2
3mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
3.4 JSON类型处理器
数据表中有一个类型为json类型的字段info的时候
步骤
定义实体UserInfo对应那个info对象
使用类型处理器
1
2
private UserInfo info;在类上开启resultMap自动转换
1
2
3
4
5
6
7
8
9
public class User {
private Long id;
private String name;
private UserInfo info;
}
四 插件功能
4.1 分页插件
新建配置类,在其中定一个bean
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MybatisConfig {
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 还可以继续添加其他插件
return interceptor;
}
}分页API
1
2
3
4
5
6
7
8
9
10
11
12
void testPageQuery() {
// 1.分页查询,new Page()的两个参数分别是:页码、每页大小
Page<User> p = userService.page(new Page<>(2, 2));
// 2.总条数
System.out.println("total = " + p.getTotal());
// 3.总页数
System.out.println("pages = " + p.getPages());
// 4.数据
List<User> records = p.getRecords();
records.forEach(System.out::println);
}其中
Page
的常见API1
2
3
4
5
6
7int pageNo = 1, pageSize = 5;
// 分页参数
Page<User> page = Page.of(pageNo, pageSize);
// 排序参数, 通过OrderItem来指定
page.addOrder(new OrderItem("balance", false));
userService.page(page);
4.2 通用分页实体
4.2.1 比较规范的用户分页查询接口
请求方式:GET
请求路径:
/users/page
请求参数:
1
2
3
4
5
6
7
8{
"pageNo": 1,
"pageSize": 5,
"sortBy": "balance",
"isAsc": false,
"name": "o",
"status": 1
}返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"total": 100006,
"pages": 50003,
"list": [
{
"id": 1685100878975279298,
"username": "user_9****",
"info": {
"age": 24,
"intro": "英文老师",
"gender": "female"
},
"status": "正常",
"balance": 2000
}
]
}
4.2.2 步骤
定义三个实体
UserQuery
:分页查询条件的实体,包含分页、排序参数、过滤条件1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserQuery extends PageQuery {
private String name;
private Integer status;
private Integer minBalance;
private Integer maxBalance;
}1
2
3
4
5
6
7
8
9
10
11
12
public class PageQuery {
private Integer pageNo;
private Integer pageSize;
private String sortBy;
private Boolean isAsc;
}PageDTO
:分页结果实体,包含总条数、总页数、当前页数据1
2
3
4
5
6
7
8
9
10
public class PageDTO<T> {
private Long total;
private Long pages;
private List<T> list;
}UserVO
:用户页面视图实体
开发接口:在
UserController
中定义分页查询用户的接口