【JAVA】黑马MybatisPlus 学习笔记【三】【拓展功能】

2023-12-25 12:41:25

3.扩展功能

3.1.代码生成

在使用MybatisPlus以后,基础的MapperServicePO代码相对固定,重复编写也比较麻烦。因此MybatisPlus官方提供了代码生成器根据数据库表结构生成POMapperService等相关代码。只不过代码生成器同样要编码使用,也很麻烦。(也就是说,我要生成代码,还要编写一套代码,让这套代码去生成代码)
这里推荐大家使用一款MybatisPlus的插件,它可以基于图形化界面完成MybatisPlus的代码生成,非常简单。

3.1.1.安装插件

Ideasetting中的plugins市场中搜索并安装MyBatisPlus插件:
在这里插入图片描述
然后重启Idea即可使用。

3.1.2.使用

刚好数据库中还有一张address表尚未生成对应的实体和mapper等基础代码。我们利用插件生成一下。
首先需要配置数据库地址,在Idea顶部菜单中,找到other,选择Config Database
在这里插入图片描述
在弹出的窗口中填写数据库连接的基本信息:
在这里插入图片描述
点击OK保存。
然后再次点击Idea顶部菜单中的other,然后选择Code Generator:

在这里插入图片描述
在弹出的表单中填写信息:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
最终,代码自动生成到指定的位置。

3.2.静态工具

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能:
在这里插入图片描述
由于Db 是静态的,无法通过泛型传递,只能通过传递字节码,得到类名

示例:

@Test
void testDbGet() {
    User user = Db.getById(1L, User.class);
    System.out.println(user);
}

@Test
void testDbList() {
    // 利用Db实现复杂条件查询
    List<User> list = Db.lambdaQuery(User.class)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000)
            .list();
    list.forEach(System.out::println);
}

@Test
void testDbUpdate() {
    Db.lambdaUpdate(User.class)
            .set(User::getBalance, 2000)
            .eq(User::getUsername, "Rose");
}

在这里插入图片描述

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

首先,我们要添加一个收货地址的VO对象:

package com.itheima.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查询用户的业务接口:

@GetMapping("/{id}")
@ApiOperation("根据id查询用户")
public UserVO queryUserById(@PathVariable("id") Long userId){
    // 基于自定义service方法查询
    return userService.queryUserAndAddressById(userId);
}

由于查询业务复杂,所以要在service层来实现。首先在IUserService中定义方法:

package com.itheima.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;

public interface IUserService extends IService<User> {
  
    UserVO queryUserAndAddressById(Long userId);
}

然后,在UserServiceImpl中实现该方法:

@Override
    public UserVO queryUserAndAddressById(Long userId) {
        // 1.查询用户
        User user = getById(userId);
        if (user == null || user.getStatus() == 2){
            throw new RuntimeException("用户状态异常");
        }
            // 2.查询收货地址
        List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, userId).list();
        // 3.封装VO
        // 3.1 user的 po 转 vo
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        // 3.2 地址的po 转vo
        if(CollUtil.isNotEmpty(addresses)){
            userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
        }

        return userVO;
    }

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

再来实现一个功能:

案例二: 根据id批量查询用户,并查询出用户对应的所有地址

修改UserController中根据id查询用户的业务接口:

 @GetMapping
    @ApiOperation("根据id集合查询用户")
    public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids){
        return userService.queryUserAndAddressByIds(ids);
    }

service层。首先在IUserService中定义方法:

package com.itheima.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;

public interface IUserService extends IService<User> {

      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.查询地址
        // 2.1. 获取用户id集合
        List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
        // 2.2. 根据用户id集合 查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();
        // 2.3.转地址VO
        List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
        // 2.4.用户地址集合 分组处理,一个用户对应一个地址集合,map结构
        Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
        if (CollUtil.isNotEmpty(addressVOList)) { // 根据用户id 进行分组
            addressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
        }
        // 3.转VO返回
        ArrayList<UserVO> list = new ArrayList<>(users.size());
        for (User user : users) {
            //  3.1. 转换User 的 po 为 vo
            UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
            list.add(userVO);
            //  3.2. 转换地址的VO
            userVO.setAddresses(addressMap.get(user.getId()));
        }
        return list;
    }

3.3.逻辑删除

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

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为true
  • 查询时过滤掉标记为true的数据
    一旦采用了逻辑删除,所有的查询和删除逻辑都要跟着变化,非常麻烦。

为了解决这个问题,MybatisPlus就添加了对逻辑删除的支持。

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

例如,我们给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 testDeleteByLogic() {
    // 删除方法与以前没有区别
    addressService.removeById(59L);
}

方法与普通更新一模一样,但是底层的SQL逻辑变了:
在这里插入图片描述
执行结果如下图:
在这里插入图片描述

查询一下试试:

@Test
void testQuery() {
    List<Address> list = addressService.list();
    list.forEach(System.out::println);
}

会发现id59的确实没有查询出来,而且SQL中也对逻辑删除字段做了判断:

在这里插入图片描述

综上, 开启了逻辑删除功能以后,我们就可以像普通删除一样做CRUD,基本不用考虑代码逻辑问题。还是非常方便的。

注意:
逻辑删除本身也有自己的问题,比如:

  • 会导致数据库表垃圾数据越来越多,从而影响查询效率
  • SQL中全都需要对逻辑删除字段做判断,影响查询效率
    因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

3.3.通用枚举

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

3.3.1.定义枚举

我们定义一个用户状态的枚举:
在这里插入图片描述
代码如下:

package com.itheima.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字段的数据类型由Integer改为UserStatus 类型:
在这里插入图片描述

要让MybatisPlus处理枚举与数据库类型自动转换,我们必须告诉MybatisPlus,枚举中的哪个字段的值作为数据库值。
MybatisPlus提供了@EnumValue注解来标记枚举属性:
在这里插入图片描述

3.3.2.配置枚举处理器

application.yaml文件中添加配置:

mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
3.3.3.测试
@Test
void testService() {
    List<User> list = userService.list();
    list.forEach(System.out::println);
}

最终,查询出的User类的status字段会是枚举类型:
在这里插入图片描述

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

在这里插入图片描述
最后,在页面查询,结果如下:
在这里插入图片描述

3.4.JSON类型处理器

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

在这里插入图片描述
格式像这样:

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

而目前User实体类中却是String类型:
在这里插入图片描述
这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map或者实体类

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

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

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

3.4.1.定义实体

首先,我们定义一个单独实体类来与info字段的属性匹配:
在这里插入图片描述
代码如下:

package com.itheima.mp.domain.po;

import lombok.Data;

@Data
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}
3.4.2.使用类型处理器

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

在这里插入图片描述
测试可以发现,所有数据都正确封装到UserInfo当中了:
在这里插入图片描述

在这里插入图片描述
同时,为了让页面返回的结果也以对象格式返回,我们要修改UserVO中的info字段:

在这里插入图片描述
此时,在页面查询结果如下:
在这里插入图片描述

文章来源:https://blog.csdn.net/qq_43322436/article/details/135064306
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。