3.扩展功能
3.1.代码生成
在使用MybatisPlus以后,基础的Mapper、Service、PO代码相对固定,重复编写也比较麻烦。因此MybatisPlus官方提供了代码生成器根据数据库表结构生成PO、Mapper、Service等相关代码。只不过代码生成器同样要编码使用,也很麻烦。
这里推荐大家使用一款MybatisPlus的插件,它可以基于图形化界面完成MybatisPlus的代码生成,非常简单。
3.1.1.安装插件
在Idea的plugins市场中搜索并安装MyBatisPlus插件, 然后重启你的Idea即可使用。
3.1.2.使用
刚好数据库中还有一张address表尚未生成对应的实体和mapper等基础代码。我们利用插件生成一下。
首先需要配置数据库地址,在Idea顶部菜单中,找到
other,选择Config Database在弹出的窗口中填写数据库连接的基本信息
然后再次点击Idea顶部菜单中的other,然后选择
Code Generator在弹出的表单中填写信息

最终,代码自动生成到指定的位置了:
3.2.静态工具
有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能:

示例:
1 |
|
需求:改造根据id用户查询的接口,查询用户的同时返回用户收货地址列表
首先,我们要添加一个收货地址的VO对象:
1 | package com.itheima.mp.domain.vo; |
然后,改造原来的UserVO,添加一个地址属性:

接下来,修改UserController中根据id查询用户的业务接口:
1 |
|
由于查询业务复杂,所以要在service层来实现。首先在IUserService中定义方法:
1 | package com.itheima.mp.service; |
然后,在UserServiceImpl中实现该方法:
1 |
|
在查询地址时,我们采用了Db的静态方法,因此避免了注入AddressService,减少了循环依赖的风险。
再来实现一个功能:
- 根据id批量查询用户,并查询出用户对应的所有地址
**3.3.**逻辑删除
对于一些比较重要的数据,我们往往会采用逻辑删除的方案,即:
- 在表中添加一个字段标记数据是否被删除
- 当删除数据时把标记置为true
- 查询时过滤掉标记为true的数据
一旦采用了逻辑删除,所有的查询和删除逻辑都要跟着变化,非常麻烦。
为了解决这个问题,MybatisPlus就添加了对逻辑删除的支持。
注意,只有MybatisPlus生成的SQL语句才支持自动的逻辑删除,自定义SQL需要自己手动处理逻辑删除。
例如,我们给address表添加一个逻辑删除字段:
1 | alter table address add deleted bit default b'0' null comment '逻辑删除'; |
然后给Address实体添加deleted字段:

接下来,我们要在application.yml中配置逻辑删除字段:
1 | mybatis-plus: |
测试: 首先,我们执行一个删除操作:
1 |
|
方法与普通删除一模一样,但是底层的SQL逻辑变了:

查询一下试试:
1 |
|
会发现id为59的确实没有查询出来,而且SQL中也对逻辑删除字段做了判断:

综上, 开启了逻辑删除功能以后,我们就可以像普通删除一样做CRUD,基本不用考虑代码逻辑问题。还是非常方便的。
注意: 逻辑删除本身也有自己的问题,比如:
- 会导致数据库表垃圾数据越来越多,从而影响查询效率
- SQL中全都需要对逻辑删除字段做判断,影响查询效率
因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。
3.3.通用枚举
User类中有一个用户状态字段:

像这种字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是int类型,对应的PO也是Integer。因此业务操作时必须手动把枚举与Integer转换,非常麻烦。
因此,MybatisPlus提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换。
3.3.1.定义枚举
我们定义一个用户状态的枚举:
1 | package com.itheima.mp.enums; |
然后把User类中的status字段改为UserStatus 类型
要让MybatisPlus处理枚举与数据库类型自动转换,我们必须告诉MybatisPlus,枚举中的哪个字段的值作为数据库值。 MybatisPlus提供了@EnumValue注解来标记枚举属性:

**3.3.2.**配置枚举处理器
在application.yaml文件中添加配置:
1 | mybatis-plus: |
3.3.3.测试
1 |
|
最终,查询出的User类的status字段会是枚举类型:

同时,为了使页面查询结果也是枚举格式,我们需要修改UserVO中的status属性
并且,在UserStatus枚举中通过@JsonValue注解标记JSON序列化时展示的字段:

最后,在页面查询,结果如下:

**3.4.**JSON类型处理器
数据库的user表中有一个info字段,是JSON类型:

格式像这样:
1 | {"age": 20, "intro": "佛系青年", "gender": "male"} |
而目前User实体类中却是String类型
这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map或者实体类。
而一旦我们把info改为对象类型,就需要在写入数据库时手动转为String,再读取数据库时,手动转换为对象,这会非常麻烦。
因此MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler处理器。
接下来,我们就来看看这个处理器该如何使用。
3.4.1.定义实体
首先,我们定义一个单独实体类来与info字段的属性匹配:
1 | package com.itheima.mp.domain.po; |
3.4.2.使用类型处理器
接下来,将User类的info字段修改为UserInfo类型,并声明类型处理器:

同时,在User类上添加一个注解,声明自动映射:

测试可以发现,所有数据都正确封装到UserInfo当中了:

同时,为了让页面返回的结果也以对象格式返回,我们要修改UserVO中的info字段
此时,在页面查询结果如下:

4.插件功能
MybatisPlus提供了很多的插件功能,进一步拓展其功能。目前已有的插件有:
PaginationInnerInterceptor:自动分页TenantLineInnerInterceptor:多租户DynamicTableNameInnerInterceptor:动态表名OptimisticLockerInnerInterceptor:乐观锁IllegalSQLInnerInterceptor:sql 性能规范BlockAttackInnerInterceptor:防止全表更新与删除
注意: 使用多个分页插件的时候需要注意插件定义顺序,建议使用顺序如下:
- 多租户,动态表名
- 分页,乐观锁
- sql 性能规范,防止全表更新与删除
这里我们以分页插件为里来学习插件的用法。
4.1.分页插件
在未引入分页插件的情况下,MybatisPlus是不支持分页功能的,IService和BaseMapper中的分页方法都无法正常起效。 所以,我们必须配置分页插件。
4.1.1.配置分页插件
在项目中新建一个配置类:
1 | package com.itheima.mp.config; |
4.1.2.分页API
编写一个分页查询的测试:
1 |
|
运行的SQL如下:

这里用到了分页参数,Page,即可以支持分页参数,也可以支持排序参数。常见的API如下:
1 | int pageNo = 1, pageSize = 5; |
4.2.通用分页实体
现在要实现一个用户分页查询的接口,接口规范如下:
| 参数 | 说明 |
|---|---|
| 请求方式 | GET |
| 请求路径 | /users/page |
| 请求参数 | { "pageNo": 1, "pageSize": 5, "sortBy": "balance", "isAsc": false, "name": "o", "status": 1 } |
| 返回值 | { "total": 100006, "pages": 50003, "list": [ { "id": 1685100878975279298, "username": "user_9****", "info": { "age": 24, "intro": "英文老师", "gender": "female" }, "status": "正常", "balance": 2000 } ] } |
| 特殊说明 | 如果排序字段为空,默认按照更新时间排序排序字段不为空,则按照排序字段排序 |
这里需要定义3个实体:
UserQuery:分页查询条件的实体,包含分页、排序参数、过滤条件PageDTO:分页结果实体,包含总条数、总页数、当前页数据UserVO:用户页面视图实体
4.2.1.实体
由于UserQuery之前已经定义过了,并且其中已经包含了过滤条件,具体代码如下:
1 | package com.itheima.mp.domain.query; |
其中缺少的仅仅是分页条件,而分页条件不仅仅用户分页查询需要,以后其它业务也都有分页查询的需求。因此建议将分页查询条件单独定义为一个PageQuery实体
PageQuery是前端提交的查询参数,一般包含四个属性:
pageNo:页码pageSize:每页数据条数sortBy:排序字段isAsc:是否升序
1 |
|
然后,让我们的UserQuery继承这个实体:
1 | package com.itheima.mp.domain.query; |
返回值的用户实体沿用之前定一个UserVO实体
最后,则是分页实体PageDTO:
1 | package com.itheima.mp.domain.dto; |
4.2.2.开发接口
我们在UserController中定义分页查询用户的接口:
1 | package com.itheima.mp.controller; |
然后在IUserService中创建queryUsersPage方法:
1 | PageDTO<UserVO> queryUsersPage(PageQuery query); |
接下来,在UserServiceImpl中实现该方法:
1 |
|
启动项目,在页面查看:

4.2.3.改造PageQuery实体
在刚才的代码中,从PageQuery到MybatisPlus的Page之间转换的过程还是比较麻烦的。
我们完全可以在PageQuery这个实体中定义一个工具方法,简化开发。 像这样:
1 | package com.itheima.mp.domain.query; |
这样我们在开发也时就可以省去对从PageQuery到Page的的转换:
1 | // 1.构建条件 |
4.2.4.改造PageDTO实体
在查询出分页结果后,数据的非空校验,数据的vo转换都是模板代码,编写起来很麻烦。
我们完全可以将其封装到PageDTO的工具方法中,简化整个过程:
1 | package com.itheima.mp.domain.dto; |
最终,业务层的代码可以简化为:
1 |
|
如果是希望自定义PO到VO的转换过程,可以这样做:
1 |
|
最终查询的结果如下:
