3 扩展功能
3.1 代码生成
- 在使用MybatisPlus以后,基础的
Mapper
、Service
、PO
代码相对固定,重复编写也比较麻烦。因此MybatisPlus官方提供了代码生成器根据数据库表结构生成PO
、Mapper
、Service
等相关代码。只不过代码生成器同样要编码使用,也很麻烦; - 这里推荐使用一款
MybatisPlus
的插件,它可以基于图形化界面完成MybatisPlus
的代码生成,非常简单。
3.1.1 安装插件
- 重启IDEA。
3.1.2 使用
-
配置数据库地址,在Idea顶部菜单中,找到
other
,选择Config Database
: -
在弹出的窗口中填写数据库连接的基本信息:
-
点击
test connect
测试连接。若报时区错误,则参考该文章MySQL如何修改时区_servertimezone=北京时间-优快云博客; -
点击OK保存。然后再次点击Idea顶部菜单中的other,然后选择
Code Generator
: -
在弹出的表中填写信息:
3.2 静态工具
-
有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:
Db
,其中的一些静态方法与IService
中方法签名基本一致,也可以实现CRUD功能: -
需求:
-
改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址;
-
添加一个收货地址的VO对象:
package com.shisan.mp.domain.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @Data @ApiModel(description = "收货地址VO") public class AddressVO{ @ApiModelProperty("id") private Long id; @ApiModelProperty("用户ID") private Long userId; @ApiModelProperty("省") private String province; @ApiModelProperty("市") private String city; @ApiModelProperty("县/区") private String town; @ApiModelProperty("手机") private String mobile; @ApiModelProperty("详细地址") private String street; @ApiModelProperty("联系人") private String contact; @ApiModelProperty("是否是默认 1默认 0否") private Boolean isDefault; @ApiModelProperty("备注") private String notes; }
-
改造原来的UserVO,添加一个地址属性:
-
修改UserController中根据id查询用户的业务接口:
@ApiOperation("根据id查询用户接口") @GetMapping("{id}") public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id){ return userService.queryUserAndAddressById(id); }
-
在IUserService中定义方法:
UserVO queryUserAndAddressById(Long id);
-
在UserServiceImpl中实现该方法:
@Override public UserVO queryUserAndAddressById(Long id) { //1、查询用户 User user = getById(id); if(user == null || user.getStatus() == 2){ throw new RuntimeException("用户状态异常!"); } //2、查询地址 List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, id).list(); //3、封装VO //转User的PO为VO UserVO userVO = BeanUtil.copyProperties(user, UserVO.class); //转地址的PO为VO if(CollUtil.isNotEmpty(addresses)){ //如果地址不为空 userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class)); } return userVO; }
-
在查询地址时,采用了Db的静态方法,因此避免了注入AddressService,减少了循环依赖的风险;
-
-
改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址;
-
修改UserController中根据id批量查询用户接口:
@ApiOperation("根据id批量查询用户接口") @GetMapping public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){ return userService.queryUserAndAddressByIds(ids); }
-
在IUserService中定义方法:
List<UserVO> queryUserAndAddressByIds(List<Long> ids);
-
在UserServiceImpl中实现该方法:
@Override public List<UserVO> queryUserAndAddressByIds(List<Long> ids) { //1、查询用户 List<User> users = listByIds(ids); if(CollUtil.isEmpty(users)){ return Collections.emptyList(); } //2、查询地址 //获取用户id List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList()); //根据用户id查询地址 List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list(); //转换地址VO List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class); //整理地址集合,将相同用户的地址放到一起 Map<Long, List<AddressVO>> addressMap = new HashMap<>(0); if(CollUtil.isNotEmpty(addressVOList)) { addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId)); } //3、转VO返回 List<UserVO> list = new ArrayList<>(users.size()); for (User user : users) { //转换User的VO为PO UserVO vo = BeanUtil.copyProperties(user, UserVO.class); list.add(vo); //转换地址VO vo.setAddresses(addressMap.get(user.getId())); } return list; }
-
-
3.3 逻辑删除
-
对于一些比较重要的数据,我们往往会采用逻辑删除的方案,即:
- 在表中添加一个字段标记数据是否被删除;
- 当删除数据时把标记置为true(即已删除);
- 查询时过滤掉标记为true的数据(即查询标记为false的数据,未被删除的数据);
-
一旦采用了逻辑删除,所有的查询和删除逻辑都要跟着变化,非常麻烦。为了解决这个问题,MybatisPlus就添加了对逻辑删除的支持;
- 注意:只有MybatisPlus生成的SQL语句才支持自动的逻辑删除,自定义SQL需要自己手动处理逻辑删除;
-
MvbatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在
application.yaml
文件中配置逻辑删除的字段名称和值即可: -
例:
-
给
address
表添加一个逻辑删除字段:alter table address add deleted bit default b'0' null comment '逻辑删除';
-
给
Address
实体添加deleted
字段:
-
在
application.yml
中配置逻辑删除字段:mybatis-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)
-
执行删除和查询操作:
@Test void testLogicDelete() { //删除 addressService.removeById(59); //查询 Address address = addressService.getById(59); System.out.println(address); }
-
方法与普通删除一模一样,但是底层的SQL逻辑变了。且会发现id为59的确实没有查询出来,而且SQL中也对逻辑删除字段做了判断:
-
综上, 开启了逻辑删除功能以后,就可以像普通删除一样做CRUD,基本不用考虑代码逻辑问题。还是非常方便的。
-
-
注意:
- 逻辑删除本身也有自己的问题,比如:
- 会导致数据库表垃圾数据越来越多,从而影响查询效率;
- SQL中全都需要对逻辑删除字段做判断,影响查询效率;
- 因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。
- 逻辑删除本身也有自己的问题,比如:
3.4 枚举处理器
-
User类中有一个用户状态字段:
-
像这种字段一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是数据库采用的是
int
类型,对应的PO也是Integer
。因此业务操作时必须手动把枚举
与Integer
转换,非常麻烦; -
因此,MybatisPlus提供了一个处理枚举的类型转换器,可以把枚举类型与数据库类型自动转换。
3.4.1 定义枚举
-
定义一个用户状态的枚举:
-
代码如下:
package com.shisan.mp.enums; import com.baomidou.mybatisplus.annotation.EnumValue; import lombok.Getter; @Getter 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; } }
-
把
User
类中的status
字段改为UserStatus
类型(UserVO
中也要改): -
要让
MybatisPlus
处理枚举与数据库类型自动转换,必须告诉MybatisPlus
,枚举中的哪个字段的值作为数据库值。MybatisPlus
提供了@EnumValue
注解来标记枚举属性: -
后端给前端默认返回枚举变量的名称,即
NORMAL
和FREEZE
。可以在字段上加上@JsonValue
注解,使后端返回对应的枚举变量的字段,此处返回正常
或冻结
:
3.4.2 配置枚举处理器
-
在application.yaml文件中添加配置:
mybatis-plus: configuration: default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
3.4.3 测试
-
修改UserServiceImpl.java:
3.5 JSON处理器
-
数据库的user表中有一个
info
字段,是JSON类型: -
格式像这样:
{"age": 20, "intro": "佛系青年", "gender": "male"}
-
而目前
User
实体类中却是String
类型: -
要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个
Map
或者实体类; -
而一旦把
info
改为对象
类型,就需要在写入数据库时手动转为String
,再读取数据库时,手动转换为对象
,这会非常麻烦; -
因此MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用
JacksonTypeHandler
处理器。
3.5.1 定义实体
-
定义一个单独实体类来与info字段的属性匹配:
-
代码如下:
package com.shisan.mp.domain.po; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor(staticName = "of") public class UserInfo { private Integer age; private String intro; private String gender; }
3.5.2 使用类型处理器
-
开启自动结果集映射,即自动映射实体类和数据库字段的映射关系:
-
将User类的info字段修改为UserInfo类型,并声明类型处理器(UserVO中也要改,但是无需声明类型处理器):
4 插件功能
-
MybatisPlus提供了很多的插件功能,进一步拓展其功能。目前已有的插件有:
PaginationInnerInterceptor
:自动分页TenantLineInnerInterceptor
:多租户DynamicTableNameInnerInterceptor
:动态表名OptimisticLockerInnerInterceptor
:乐观锁IllegalSQLInnerInterceptor
:sql 性能规范BlockAttackInnerInterceptor
:防止全表更新与删除
-
注意: 使用多个分页插件的时候需要注意插件定义顺序,建议使用顺序如下:
- 多租户,动态表名
- 分页,乐观锁
- sql 性能规范,防止全表更新与删除