用户管理 1:模糊查询及分页查询

前言

上一篇我们完成了对服务器存储空间的优化,本篇开始,进入后台管理功能开发阶段。本篇重点带领大家实现 MyBatis 的分页查询和自定义查询功能,同时对 MyBatis 通用 Mapper 中现存的一些问题给予修正。

功能分析

通常,后台管理系统中常用到分页查询及模糊查询。要求有接口权限限制,避免非管理员用户访问到仅管理员可以访问的接口。本专栏前面花费了 6 篇内容带领大家搭建起 Keller Notes 项目的服务端架构,现在,我们结合项目中现有的服务端架构思考以下几个问题:

  1. 现有架构是否能准确区分用户身份?
  2. 现有架构是够能实现接口权限的控制?
  3. 当前的通用 Mapper 是否能支持分页查询?
  4. 当前的通用 Mapper 是否能支持模糊查询及自定义查询?
  5. 当前的通用 Mapper 在设计上是否存在缺陷?

用户身份判断:

  1. 这设计 JWT 时,存入了用户类型。
  2. 可以在 JWT 验证完毕后,取出用户类型,用来区分普通用户和管理员。

接口权限控制:

  1. 需要请求者的用户 ID 的接口中,参数名都固定为 kellerUserId,且该参数无法通过请求中携带的查询参数、Body 内容指定,只解析 JWT 中存储的用户 ID。
  2. 按照相同的思路,只需要在仅管理员可访问的接口中,将用户 ID 的参数名固定为 kellerAdminId 即可,并限制为:只能从 JWT 中取出管理员的用户 ID 作为该参数。
  3. 当前,用户登录后生成的 JWT 使用 localStorage 永久保存在用户本地。而管理员的 JWT 所需的安全等级更高,需要保存在 sessionStorage 中,仅会话打开状态生效,关闭会话后清除。

分页查询:

  1. 在 BaseEntity.java 中,定义了属性 baseKyleUseAnd、baseKyleUseASC 分别用来指定查询时多条件的连接方式、查询时的排序方式等。
  2. 按照同样的思路,只需要增加 page、rowNum 等参数,即可使其支持分页查询。

模糊查询及自定义查询:

  1. 通用 Mapper 当前的设计已经支持了自定义查询,但仅限于部分字段的等值查询,暂时不支持模糊查询和范围查询。
  2. 结合 MyBatis 的灵活性,可以通过指定查询条件的方式支持各种形式的自定义查询模糊查询。只需要将自定义的查询条件书写在 Mapper 中,同时在 BaseEntity.java 中添加属性,接受自定义的查询条件即可。

当前通用 Mapper 存在的缺陷:

  1. 在设计通用 Mapper 时,使用 @IndexAttribute 注解标注的字段被指定为索引,且在查询时,该字段有值时,会被作为查询条件。
  2. 这就引出一个问题:是否经常作为查询条件的字段都要指定为索引?答案显然不是的。如:用户类型、用户状态等字段,经常作为查询条件,但其值重复性大,不适合被指定为索引。因此,将索引字段和查询条件分开设计,索引字段和查询字段若有值,就作为查询条件。

代码实现

查询条件与索引分开

设计方案为:

  • 删除 @IndexAttribute 注解,在 @FieldAttribute 注解中添加 isIndex 属性用来标示该字段为索引字段,添加 isCondition 属性用来标示该字段为查询字段。
  • 检查每个 Entity,分析每个 @IndexAttribute 注解标注的字段:
    • 适合设置为索引的字段,将其 @FieldAttribute 注解中的 isIndex 设置为 true,将该字段创建为索引;
    • 不适合设置为索引的字段,将其 @FieldAttribute 注解中的 isCondition 设置为 true,可作为查询条件使用。
  • 在创建索引时,将判断条件为带有 @IndexAttribute 注解的字段,修改为 @FieldAttribute 注解中 isIndex 为 true 的字段。
  • 在创建查询条件时,将判断条件为 @IndexAttribute 注解标注的有值字段,修改为 @FieldAttribute 注解中 isIndex 为 true 或 isCondition 为 true 的字段。

具体实现如下。

在 FieldAttribute.java 中添加 isIndex 属性和 isCondition 属性:

      /**
     * 是否是索引字段,如果是索引字段,在建表时会以该字段创建索引,在查询时,字段有值会作为查询条件使用
     * @return
     */
    boolean isIndex() default false;

    /**
     * 是否要作为查询条件,如果作为查询条件,该字段若有值,会作为查询条件使用
     * @return
     */
    boolean isCondition() default false;

在 Entity 中修改,以 UserInfo.java 举例:type 字段常用来做查询条件,却不适合创建索引,因此在其 @FieldAttribute 注解中添加 isCondition = true;email 字段常用来做查询条件,也适合创建索引,因此在其 @FieldAttribute 注解中添加 isIndex = true

        @FieldAttribute(value = "用户类型",notNull = true,isCondition = true)
    private Integer type;


    @FieldAttribute(value = "密码",length = 200, isDetailed = true)
    private String password;

    @FieldAttribute(value = "邮箱",notNull = true,length = 200,isIndex = true)
    private String email;

    @FieldAttribute
    private Date createTime = new Date();

    @FieldAttribute(value = "用户账号状态",isCondition = true)
    private Integer status ;

修改创建索引的条件,在 SqlFieldReader.java 的 getCreateIndexSql 方法中做如下修改:

public static <T extends BaseEntity> String getCreateIndexSql(T entity){

        ... ...
                      // 带有 @FieldAttribute 注解,且 isIndex 为 true 时才创建索引
            FieldAttribute fieldAttribute = field.getAnnotation(FieldAttribute.class);
            if(fieldAttribute != null && fieldAttribute.isIndex()){
                            .... ...
            }
        }
        return builder.toString();
    }

修改拼接的查询条件,在 SqlFieldReader.java 的 getConditionSuffix 方法中做如下修改:

public static <T extends BaseEntity> String getConditionSuffix(T entity){
        ... ...
        StringBuilder builder = new StringBuilder();
        builder.append(" WHERE ");
        try {
            FieldAttribute fieldAttribute;
            for(Field field:fields){
                fieldAttribute = field.getAnnotation(FieldAttribute.class);
                if(fieldAttribute == null){
                    continue;
                 }
                //索引字段或设置为查询条件的字段中,有值的都作为查询条件
                if(fieldAttribute.isIndex() || fieldAttribute.isCondition()){
                    if(SqlFieldReader.hasValue(entity,field.getName())){
                        builder.append(field.getName())
                                .append(" = #{").append(field.getName()).append("} ")
                                .append(condition).append(" ");
                    }
                }
            }
            ... ...
    }
分页查询

分页查询的设计方案为:

  • 在 BaseEntity.java 中添加 baseKylePageSize、 baseKyleCurrentPage 字段,分别表示页面大小和当前要查询的页码;添加 baseKyleStartRows 字段,用来表示分页查询的起始行号
  • 每次设置 baseKylePageSize、baseKyleCurrentPage 字段时,都要重新计算下 baseKyleStartRows 字段的值
  • 在 BaseSelectProvider.java 中添加方法 selectPageList 用于支持分页查询

具体实现如下。

添加分页查询必要的参数,在 BaseEntity.java 中添加相应属性:

        /**
     * 页面大小
     */
    private int baseKylePageSize = 10;
    /**
     * 要查询的页码
     */
    private int baseKyleCurrentPage = 1;
    /**
     * 根据页面大小和要查询的页码计算出的起始行号
     */
    private int baseKyleStartRows ;

        ... ...

    // 每次设置是页面大小时,重新计算起始行号
    public void setBaseKylePageSize(int baseKylePageSize) {
        this.baseKylePageSize = baseKylePageSize;
        this.baseKyleStartRows = this.baseKylePageSize * (this.baseKyleCurrentPage - 1);
    }
        // 每次设置当前页码时,重新计算起始行号
    public void setBaseKyleCurrentPage(int baseKyleCurrentPage) {
        this.baseKyleStartRows = this.baseKylePageSize * (this.baseKyleCurrentPage - 1);
        this.baseKyleCurrentPage = baseKyleCurrentPage;
    }

因为 MySQL 中分页查询的语法为 limit offset,rows,也就是说需要起始行号、查询的行数这两个参数;而我们常用的表述方式为:每页显示 10 条数据、查询第 5 页,用到的参数为:页面大小、当前页码。

因此,在做分页查询时,只需要设置 baseKylePageSize、baseKyleCurrentPage 这两个参数,而实际拼接查询语句时,使用的是 baseKyleStartRows、baseKylePageSize 这两个参数。

实现分页查询方法,在 BaseSelectProvider.java 中实现分页查询方法,该方法基于 selectList,因此支持 selectList 支持的各种查询条件:

    public static <T extends BaseEntity> String selectPageList(T entity){
        String sql = selectList(entity) + " LIMIT #{baseKyleStartRows},#{baseKylePageSize}";
        Console.info("selectPageList",sql,entity);
        return sql;
    }

至此,就完成了对于分页查询的支持,在项目中只需要按照如下格式使用即可:

    // 构建 Entity 对象(必须继承 BaseEntity 类)
    UserInfo userInfo = new UserInfo();
    // 设置要查询的页码(当前页码)
  userInfo.setBaseKyleCurrentPage(page);
    // 设置每页要显示的记录行数(页面大小)
  userInfo.setBaseKylePageSize(pageSize);
    // 执行分页查询并获得查询结果
  List<UserInfo> list = userMapper.baseSelectPageList(userInfo);
支持自定义查询条件

这里提到的自定义查询主要是针对非等值查询而言的,因为当前通用 Mapper 的设计已经支持了多字段自由组合的自定义等值查询。自定义查询的设计方案如下:

  • 在 BaseEntity.java 中添加参数,用于接受自定义的查询条件
  • 在 Mapper 中书写自定义查询条件
  • 在使用时,指定自定义查询条件
  • 在生成 SQL 语句中的查询条件时,判断是否设置了自定义查询条件
    • 如果设置了查询条件,只取设置的查询条件
    • 否则,按照原来的规则构建查询条件

具体使用如下。

设置参数接收自定义查询条件,在 BaseEntity.java 中添加参数 baseKyleCustomCondition,用于接收自定义查询条件:

      /**
     * 自定义查询条件,指定自定义查询条件后,执行查询语句时,只会选择自定义的查询条件执行,忽略其他条件
     */
    private String baseKyleCustomCondition = null;

以用户名模糊查询为例,在 UserMapper.java 中书写查询条件,只用写 WHERE 后面的语句即可,格式与 MyBatis 中 SQL 的格式保持一致:

@Mapper
public interface UserMapper extends BaseMapper<UserInfo> {

    /**
     * 自定义查询条件:按邮箱模糊查询
     * 按照 MyBatis 的 SQL 格式书写
     * 该条件通过baseEntity.setBaseKyleCustomCondition设置
     */
    String whereEmailLike = "email  LIKE CONCAT('%',#{email},'%') ";
}

以用户名模糊查询为例,使用时指定自定义查询条件:

UserInfo userInfo = new UserInfo();
// 因为自定义查询条件中是按照 emial 进行模糊匹配的,因此要设置 email 参数
userInfo.setEmail(email);
// 指定自定义查询条件为按照邮箱模糊匹配
userInfo.setBaseKyleCustomCondition(UserMapper.whereEmailLike);
// 执行自定义查询并返回查询结果
List<UserInfo> list = userMapper.baseSelectList(userInfo);

在 SqlFieldReader.java 中 getConditionSuffix 方法做如下修改:

public static <T extends BaseEntity> String getConditionSuffix(T entity){
        //如果设置了自定义的查询条件,直接返回自定义查询条件,不再判断字段值
        if(entity.getBaseKyleCustomCondition() != null){
            return " WHERE " + entity.getBaseKyleCustomCondition();
        }
        ... ...
}

至此,通用 Mapper 支持了自定义查询条件及分页查询。

效果测试

我们以用户管理中常用的“用户名模糊查询”为例,测试通用 Mapper 对自定义查询条件和分页查询的支持,同时该功能也是下一篇要用到的。

新建 AdminController.java,用于处理管理员相关功能的请求,添加获取用户列表的接口:

@RestController
@RequestMapping("/admin")
public class AdminController {
    @Resource
    private UserService userService;

    @GetMapping("/userList")
    public ResponseEntity getUserList(Integer kellerAdminId,Integer page,Integer size,String email){

        if(StringUtils.isEmpty(kellerAdminId,page,size)){
            return Response.badRequest();
        }
        return Response.ok(userService.getUserList(page,size,email));
    }
}

在 UserService.java 中添加方法,用于获取用户列表:判断传入参数中是否有 email 参数,如果有,就按照邮箱名模糊查询;否则分页查询所有用户列表。

public ResultData getUserList(int page,int pageSize,String email){
        UserInfo userInfo = new UserInfo();
        userInfo.setBaseKyleCurrentPage(page);
        userInfo.setBaseKylePageSize(pageSize);
        userInfo.setBaseKyleDetailed(false);
        //如果传入了email参数,则执行模糊查询
        if(StringUtils.noEmpty(email)){
            userInfo.setEmail(email);
              // 设置自定义查询条件
            userInfo.setBaseKyleCustomCondition(UserMapper.whereEmailLike);
        }
        List<UserInfo> list = userMapper.baseSelectPageList(userInfo);
        return ResultData.success(list);
    }

请求头:

head

请求参数及返回结果:

result

源码地址

本篇完整的源码地址:

https://github.com/tianlanlandelan/KellerNotes

小结

本篇实现分页查询与自定义条件查询,为后台管理功能中常用到的模糊查询及范围查询做准备,并修正现有通用 Mapper 使用中存在的问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值