【MyBatis Plus】MyBatis Plus 扩展:利用代码生成器自动生成代码,Db 静态工具类的使用,逻辑删除,以及枚举和 JSON 处理器的使用


一、自动生成代码

在学习了 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 的用户信息
 */
@Test
void testDbGet() {
    User user = Db.getById(1L, User.class);
    System.out.println(user);
}

/**
 * 查询用户名中带 “o”, 并且balance >= 1000 的用户信息
 */
@Test
void testDbList() {
    List<User> list = Db.lambdaQuery(User.class)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000).list();
    System.out.println(list);
}

/**
 * 设置用户名为 Rose 的用户的 balance 为 2500
 */
@Test
void 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中实现该方法:
    @Override
    public 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. 处理 VO
        UserVO 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中实现该方法:
    @Override
    public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
        // 1. 查询用户集合
        List<User> users = this.listByIds(ids);
        if (users.isEmpty()) {
            return Collections.emptyList();
        }
    
        // 2. 查询地址
        // 2.1 获取用户id
        List<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 地址转换为 VO
        List<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 ,没有逻辑删除

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

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

运行这段代码:

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


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

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

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

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

四、枚举处理器

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


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

4.1 定义枚举常量

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

@Getter
public 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
@AllArgsConstructor
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

5.2 使用类型处理器

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

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

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


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

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


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

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

求知.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值