文章目录

  • 一、自动生成代码
    • 1.1 安装插件
    • 1.2 生成代码
  • 二、Db 静态工具类
    • 2.1 对 Db 静态工具类的认识
    • 2.2 Db 静态工具类的使用案例
  • 三、逻辑删除
  • 四、枚举处理器
    • 4.1 定义枚举常量
    • 4.2 配置枚举处理器
    • 4.3 测试枚举处理器的字段转换
  • 五、JSON 处理器
    • 5.1 定义实体
    • 5.2 使用类型处理器

一、自动生成代码

在学习了 MyBatis Plus 的使用之后,我们发现了基础的 MapperServicePO 等等代码基本上都是固定的,如果这样的话重复的编写代码就显得非常麻烦了。恰好,MyBatis Plus 官方就提供了代码生成器来根据数据库的表结构来自动为我们生成 MapperServicePO 相关的代码。只不过代码生成器同样要编码使用,也很麻烦。这里推荐大家使用一款 Mybatis Plus 的插件,它可以基于图形化界面完成 Mybatis Plus 的代码生成,非常简单。

1.1 安装插件

在 IDEA 的 plugins 中搜索 “Mybatis Plus”,选择其中那个图标特别可爱的就是了:


安装成功之后,可以在 IDEA 的导航栏中发现看到一个 Orther 选项:

其中就包括了数据库的配置以及生成代码的选项了。

1.2 生成代码

此时正好我们有一个 address 表还没有编写对应的代码的,此时我们可以使用这个插件来自动生成 address 表相关的代码。

  1. 首先配置数据库,弹出的窗口中填写数据库连接的基本信息:

  1. 然后再次点击 IDEA 导航栏中的 other,然后选择生成代码:

点击 “code generatro” ,就会自动生成代码到指定位置。

二、Db 静态工具类

2.1 对 Db 静态工具类的认识

有的时候不同的 Service 类之间会相互调用,为了避免出现循环依赖问题,Mybatis Plus 提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现 CRUD 的功能:

例如,下面的使用实例:

/** * 获取id为 1 的用户信息 */@Testvoid testDbGet() {User user = Db.getById(1L, User.class);System.out.println(user);}/** * 查询用户名中带 “o”, 并且balance >= 1000 的用户信息 */@Testvoid testDbList() {List<User> list = Db.lambdaQuery(User.class).like(User::getUsername, "o").ge(User::getBalance, 1000).list();System.out.println(list);}/** * 设置用户名为 Rose 的用户的 balance 为 2500 */@Testvoid testDbUpdate() {Db.lambdaUpdate(User.class).set(User::getBalance, 2500).eq(User::getUsername, "Rose").update();}

可以发现,对应 Db 静态类的使用和前面的使用方法都是类似的。

2.2 Db 静态工具类的使用案例

示例一:改造根据 id 用户查询的接口,查询用户的同时返回用户收货地址列表。

  1. 首先,我们要添加一个收货地址的 VO 对象:
@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;}
  1. 然后,改造原来的 UserVO 类,在最后添加一个地址属性:

  1. 接下来,修改UserController中根据id查询用户的业务接口:
@GetMapping("/{id}")@ApiOperation("根据id查询用户接口")public UserVO queryUserById(@PathVariable("id") Long id) {return userService.queryUserAndAddressById(id);}

此时,新增了一个queryUserAndAddressById方法。

  1. service 层实现queryUserAndAddressById方法

    • 首先在 IUserService中定义方法:
    public interface IUserService extends IService<User> {UserVO queryUserAndAddressById(Long id);}
    • 然后,在UserServiceImpl中实现该方法:
    @Overridepublic UserVO queryUserAndAddressById(Long id) {// 1. 查询用户User user = getById(id);// 2. 使用 Db 根据用户查询地址类别List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, user.getId()).list();// 3. 处理 VOUserVO userVO = BeanUtil.copyProperties(user, UserVO.class);userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));return userVO;}

在查询地址时,使用用了 Db 中的静态方法,因此避免了注入 AddressService,减少了循环依赖的风险。

完成上上面的代码之后,通过 id 查询的用户信息中就有了地址信息了:

示例二:改造根据 id 批量查询用户的接口,要求查询出用户对应的所有地址

  1. 修改 controller 接口:
@GetMapping@ApiOperation("根据id批量查询用户接口")public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {return userService.queryUserAndAddressByIds(ids);}
  1. service 层实现queryUserAndAddressByIds方法

    • 首先在 IUserService中定义方法:
    public interface IUserService extends IService<User> {List<UserVO> queryUserAndAddressByIds(Long id);}
    • 然后,在UserServiceImpl中实现该方法:
    @Overridepublic List<UserVO> queryUserAndAddressByIds(List<Long> ids) {// 1. 查询用户集合List<User> users = this.listByIds(ids);if (users.isEmpty()) {return Collections.emptyList();}// 2. 查询地址// 2.1 获取用户idList<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());// 2.2 查询地址List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();// 2.3 地址转换为 VOList<AddressVO> addressVOS = BeanUtil.copyToList(addresses, AddressVO.class);// 2.4 按照 userId 将地址 VO 进行分组Map<Long, List<AddressVO>> addressesMap = new HashMap<>();if (CollUtil.isNotEmpty(addresses)) {addressesMap = addressVOS.stream().collect(Collectors.groupingBy(AddressVO::getUserId));}// 3. 转换 VO 返回List<UserVO> list = new ArrayList<>(users.size());for (User user : users) {UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);// 填充地址userVO.setAddresses(addressesMap.get(user.getId()));list.add(userVO);}return list;}

注意事项:

  1. 在使用查询到的用户的id去查询地址信息的时候,要避免在循环中查询数据库。因此首先获取用户 id 集合,然后再根据这些 id 集合批量查询地址信息。

  2. 将查询出的地址信息按照用户 id 进行分类,然后设置进对应的 UserVO 对象中。

三、逻辑删除

对于一些比较重要的数据,我们往往会采用逻辑删除的方案,例如:

  • 在表中添加一个字段标记数据是否被删除

  • 当删除数据时把标记置为true

  • 查询时过滤掉标记为true的数据

一旦采用了逻辑删除,所有的查询和删除逻辑都要跟着变化,那么对应数据库的操作就会变得非常麻烦。幸运的是,为了解决这个麻烦,MyBatis Plus 就提供了对逻辑删除的支持。

注意,只有 Mybatis Plus 生成的SQL语句才支持自动的逻辑删除,自定义 SQL 还是需要自己手动处理逻辑删除。

下面演示使用 MyBatis Plus 的逻辑删除功能:

  1. 在给 address 表添加一个逻辑删除字段:
alter table address add deleted bit default b'0' null comment '逻辑删除';
  1. 然后给Address实体添加deleted字段:
  2. 接下来,在application.yml中配置逻辑删除字段
mybatis-plus:global-config:db-config:logic-delete-field: deleted # 逻辑删除字段logic-delete-value: 1 # 当 deleted 的值为 1,就逻辑删除了logic-not-delete-value: 0 # 当 deleted 的值为 0 ,没有逻辑删除

当完成了上面所有的准备工作之后,我们可以执行一个删除的测试方法:

@Testvoid testLogicDelete() {addressService.removeById(59L);}

运行这段代码:

发现将 id 为 59 的地址信息的 deleted 字段设置为了1,如果此时再查询这条数据:


发现此时就查询不到这条数据了,但是还在数据库中还仍然存在。

因此开启了逻辑删除功能以后,我们就可以像普通删除一样做 CRUD,基本不用考虑代码逻辑问题。还是非常方便的。但是使用逻辑删除也存在一定的问题,比如:

  • 会导致数据库表垃圾数据越来越多,从而影响查询效率;
  • SQL中全都需要对逻辑删除字段做判断,影响查询效率。

因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

四、枚举处理器

User 实体类中有一个用户状态字段:


像这种字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是 int 类型,对应的 PO 也是Integer。因此业务操作时必须手动把枚举与 Integer 转换,非常麻烦。因此,Mybatis Plus提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换。

4.1 定义枚举常量

首先,我们为用户表中的这个状态字段定义一个枚举常量:

@Getterpublic enum UserStatus {NORMAL(1, "正常"),FROZE(2, "冻结"),;private final int value;private final String desc;UserStatus(int value, String desc) {this.value = value;this.desc = desc;}}

然后把 User 类中的 status 字段改为 UserStatus 类型:

要让 Mybatis Plus处理枚举与数据库类型自动转换,我们必须告诉 Mybatis Plus,枚举中的哪个字段的值作为数据库值。Mybatis Plus 提供了 @EnumValue 注解来标记枚举属性:

4.2 配置枚举处理器

application.yml 文件中添加以下配置,以开启枚举处理器的功能:

mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler # 枚举处理器

4.3 测试枚举处理器的字段转换

例如,根据id查询某个用户:
此时,查询出的 User 类的 status 字段会是枚举类型。

同时,为了使页面查询结果也是枚举格式,我们需要修改 UserVO 中的status属性:

并且,在UserStatus枚举中通过@JsonValue注解标记 JSON 序列化时展示的字段是 desc


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

五、JSON 处理器

数据库的user表中有一个info字段,是 JSON 类型:

格式就像这样:

{"age": 20, "intro": "佛系青年", "gender": "male"}

但是目前User实体类中却是String类型,因为在 Java 中没有 JSON 这样的类型:

这样一来,我们要读取 info 中的属性时就非常不方便。如果要方便获取,info 的类型最好是一个 Map 或者实体类。

而一旦我们把info改为对象类型,就需要在写入数据库时手动转为 String,再读取数据库时,手动转换为对象,这会非常麻烦。

因此 Mybatis Plus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理 JSON 就可以使用 JacksonTypeHandler 处理器。

接下来,我们就来看看这个处理器该如何使用。

5.1 定义实体

首先,我们定义一个单独实体类来与 info 字段的属性匹配:

@Data@NoArgsConstructor@AllArgsConstructorpublic class UserInfo {private Integer age;private String intro;private String gender;}

5.2 使用类型处理器

接下来,将 User 类的 info 字段修改为 UserInfo 类型,并声明类型处理器:

注意,需要设置autoResultMaptrue,才能生效。

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


同时,为了让页面返回的结果也以对象格式返回,我们要修改UserVO中的info字段的类型:

此时,在页面查询结果如下:


发现,此时显示的 info 就是 JSON 格式了。