MyBatis-Plus —— 扩展功能详解

一、前言

上一节已经将mp的基本核心用法讲到了,除此之外,mp还具备一些能够提高开发效率的扩展功能,下面将一一讲解。

二、代码生成工具

这个需要基于插件来实现,我使用的是官方推荐的MyBatisX,这个插件中具备代码生成器,可以允许对一个单表进行代码生成。

随后就会自动生成这些文件(里面是空的,业务代码当然需要自己去写,主要目的是简化IService的实现和实现类的继承):

三、Db静态工具

静态工具的功能主要在于可以直接操作表,在简单的多表查询中,可以无需注入Mapper,实现直接从表中查询数据,然后再拼接数据的功能。Db本身的方法和其他构造器的方法相同,所以没有什么学习成本,直接使用即可。

例如:

需求1:根据id查询用户和用户对应的所有地址:

java 复制代码
@Override
    public UserVO queryUserAndAddressById(Long id) {
        //1.查询用户
        User user = this.getById(id);
        if (user == null || user.getStatus() == UserStatus.FROZEN) {
            throw new RuntimeException("用户状态异常");
        }
        //2.查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class)
                .eq(Address::getUserId, id)
                .list();
        //3.封装VO
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        if (CollUtil.isNotEmpty(addresses)) {
            userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
        }
        return userVO;
    }

这里就用Db根据userId条件查询地址集合,然后将集合拼接到userVO中去。

需求2:根据ids批量查询用户和用户对应的所有地址:

java 复制代码
@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);
        Map<Long, List<AddressVO>> addressMap = new HashMap<>(0);
        //2.4 梳理地址集合,分类整理,相同用户的放入一个集合中
        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) {
            //3.1 转换User的PO为VO
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            list.add(vo);
            //3.2 转换地址VO
            vo.setAddresses(addressMap.get(user.getId()));
        }
        return list;
    }

四、处理器

先展示一下我们的User类:

java 复制代码
@Data
@TableName(value = "user", autoResultMap = true)
public class User {

    /**
     * 用户id
     */
    @TableId(type = IdType.AUTO)//标记自增长,如果这里不标记id就是默认雪花算法
    private Long id;

    /**
     * 用户名
     */
    @TableField("`username`")//这里只是使用了转义字符,当表中的字段名和成员变量名不同(或者没有遵循驼峰-下划线规则)时可以用这个注解绑定
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 注册手机号
     */
    private String phone;

    /**
     * 详细信息
     */
    private Stringinfo;

    /**
     * 使用状态(1正常 2冻结)
     */
    private String status;

    /**
     * 账户余额
     */
    private Integer balance;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;


    /**
     * 测试表外字段
     */
    @TableField(exist = false)//表示这个字段不是表中的字段
    private String redundantField;
}

1.枚举处理器

以往在苍穹外卖中,我们通常使用的是常量类来统一管理常量,这里引入枚举,这是更加常见的常量管理办法。可以看到下面我们定义了两个常量(枚举实例,必须具备下面声明的字段value和desc),一个是NORMAL,一个是FROZEN。

我们在这里需要解决的问题是,如何将枚举类的数据放入数据库中,这里我们就可以通过注解来解决这个问题,

@EnumValue 标记value字段,这是存入数据库的值,后续操作也是操作的这个值。

@JsonValue是标记JSON序列化后需要转化成的值,例如后续我们在输出用户状态时,我们将以一个JSON格式输出,输出的就是 {"status": "正常"} ,而不是{"status": "NORMAL"} 或 {"status": 1}

java 复制代码
@Getter
public enum UserStatus {
    NORMAL(1,"正常"),
    FROZEN(2,"冻结"),
    ;
    @EnumValue
    private final int value;
    @JsonValue
    private final String desc;


    UserStatus(int value,String desc) {
        this.value = value;
        this.desc = desc;
    }
}

所以我们的User可以改成:

java 复制代码
     /**
     * 使用状态(1正常 2冻结)
     */
    private UserStatus status;

2.JSON处理器

我们原先是使用String来展示userInfo的,现在我们使用一个类来描述userInfo:

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class UserInfo {
    private  Integer age;
    private String intro;
    private String gender;
}

所以User可以改为:

java 复制代码
    /**
     * 详细信息
     */
    
    private UserInfo info;

这样就会出现一个问题,我们应该怎样把这个类存储进数据库,那肯定可以想到是用JSON格式字符串存储,那就只有一个问题了,就是如何转换,以往我们是通过一个工具类手动转换的,很麻烦,这里我们可以使用新的解决方法------mp提供了注解:

java 复制代码
    /**
     * 详细信息
     */
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;

这就是JSON处理器了,这个注解标记让这个成员变量转换成JSON格式(在更新和插入时序列化、查询时反序列化),参数是转换器类(可自选不同公司的处理器)

但是别忘了使处理器生效,在User类上添加这个注解,让结果集自动映射:

java 复制代码
@TableName(value = "user", autoResultMap = true)

五、分页插件的使用

mp的分页插件的使用其实就是让我们自己写一个pageHelper,Page类是mp提供的工具类,用于封装分页的信息。

1.整体思路

首先需要在配置类中添加分页插件,在这里可以设置最大的分页数、使用的数据库类型等等。

java 复制代码
@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        //1.创建分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInnerInterceptor.setMaxLimit(1000L);
        //2.添加分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}

然后创建分页所需的DTO和query:

这里注意辨析:

1**.Page**是核心类,用于存放分页参数、存放查询结果(所以通常会创建两个Page的对象)。

2.PageQuery 是用于存放前端传回的请求参数的类。

3.PageDTO 是存放返回结果的类。

java 复制代码
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;
}
java 复制代码
@Data
public class PageQuery {
    private Integer pageNo;
    private Integer pageSize;
    private String sortBy;
    private Boolean isAsc;
}

共分为四步:

  1. 构造分页条件(页码、大小、排序)

  2. 进行分页查询

  3. 封装VO结果

  4. 返回分页DTO

java 复制代码
    @Override
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();

        //1.构造查询条件
        //1.1分页条件
        Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
        //1.2排序条件
        if (StrUtil.isNotBlank(query.getSortBy())) {
            //不为空
            page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
        }else {
            //为空,默认更新时间排序
            page.addOrder(new OrderItem("update_time", false));
        }

        //2.分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);

        //3.封装VO结果
        PageDTO<UserVO> dto = new PageDTO<>();
        //3.1总条数
        dto.setTotal(p.getTotal());
        //3.2总页数
        dto.setPages(p.getPages());
        //3.3当前页数据
        List<User> records = p.getRecords();
        if(CollUtil.isEmpty(records)){
            dto.setList(Collections.emptyList());
            return dto;
        }
        //3.4拷贝user的VO
        List<UserVO> vos = BeanUtil.copyToList(records, UserVO.class);
        dto.setList(vos);
        //4.返回
        return dto;
    }

这里比较让人疑惑的一点,就是为什么会创建两个Page的对象,这是因为在使用lambda构造器时,我希望查出来的数据直接就封装成一个page对象,但是这当然需要按照一定规则了,比如当前页数和每一页的条数,所以我们将这个规则单独封装成一个Page对象,后续要封装查询的对象时,我们将这个规则对象作为参数传入构造器,于是就能按照这个规则拿到查询后的每一页的结果集了,所以第二个Page对象我们用于存放查询结果。

2.封装

可以看到上面的代码很多而且很繁琐,这个时候肯定会选择封装,我们将上面部分封装到PageDTO中的一个方法中去,下面部分封装到PageQuery中去:

java 复制代码
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Long total;
    @ApiModelProperty("总页数")
    private Long pages;
    @ApiModelProperty("集合")
    private List<T> list;

    public static<PO,VO> PageDTO<VO> of(Page<PO> p ,Class<VO> clazz){
        //封装VO结果
        PageDTO<VO> dto = new PageDTO<>();
        //1总条数
        dto.setTotal(p.getTotal());
        //2总页数
        dto.setPages(p.getPages());
        //3当前页数据
        List<PO> records = p.getRecords();
        if(CollUtil.isEmpty(records)){
            dto.setList(Collections.emptyList());
            return dto;
        }
        //4拷贝user的VO
        List<VO> vos = BeanUtil.copyToList(records, clazz);
        dto.setList(vos);
        return dto;
    }
}
java 复制代码
@Data
public class PageQuery {
    private Integer pageNo;
    private Integer pageSize;
    private String sortBy;
    private Boolean isAsc;

    public <T>  Page<T> toMpPage(OrderItem ... orders){
        // 1.分页条件
        Page<T> p = Page.of(pageNo, pageSize);
        // 2.排序条件
        // 2.1.先看前端有没有传排序字段
        if (sortBy != null) {
            p.addOrder(new OrderItem(sortBy, isAsc));
            return p;
        }
        // 2.2.再看有没有手动指定排序字段
        if(orders != null){
            p.addOrder(orders);
        }
        return p;
    }

    public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
        return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
    }

    public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
        return toMpPage("create_time", false);
    }

    public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
        return toMpPage("update_time", false);
    }
}

最终我们的Service就简化为了:

java 复制代码
@Override
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name = query.getName();
        Integer status = query.getStatus();

        //由于在PageQuery类中我们将上述步骤封装成了泛型,所以这里一行代码就可以实现了
        Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();
        //分页查询
        Page<User> p = lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .page(page);
       
        return PageDTO.of(p,UserVO.class);
    }
相关推荐
Dreamboat¿1 小时前
SQL 注入漏洞
数据库·sql
曹牧2 小时前
Oracle数据库中,将JSON字符串转换为多行数据
数据库·oracle·json
被摘下的星星2 小时前
MySQL count()函数的用法
数据库·mysql
末央&2 小时前
【天机论坛】项目环境搭建和数据库设计
java·数据库
徒 花2 小时前
数据库知识复习07
数据库·作业
素玥2 小时前
实训5 python连接mysql数据库
数据库·python·mysql
jnrjian3 小时前
text index 查看index column index定义 index 刷新频率 index视图
数据库·oracle
瀚高PG实验室3 小时前
审计策略修改
网络·数据库·瀚高数据库
言慢行善3 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
韶博雅3 小时前
emcc24ai
开发语言·数据库·python