对于 TypeORM 精准&模糊查询的简单二次封装

文章目录

  • [对于 Typeorm 模糊查询的简单二次封装](#对于 Typeorm 模糊查询的简单二次封装)
    • 代码
    • 使用实例
      • [定义一个 filterDto](#定义一个 filterDto)
      • [Service 中使用](#Service 中使用)

对于 Typeorm 模糊查询的简单二次封装

笔者目前在做 NestJS + TyoeORM 项目,需要对查询逻辑进行二次封装,以适应多变的查询需求。本文将通过一个具体的示例------addQueryFilterConditions 函数,探讨如何在 TypeORM 中实现精确匹配和模糊查询的灵活结合,从而提升查询的灵活性和可维护性。

在一般项目开发中,数据过滤条件通常由前端传递给后端,而后端需要根据这些条件动态生成 SQL 查询语句。addQueryFilterConditions 函数正是为此目的而生,它接收查询构造器 (SelectQueryBuilder)、别名 (alias)、过滤数据传输对象 (filterDto) 以及查询选项 (options),根据这些参数动态添加 WHERE 条件到查询中,支持精确匹配和模糊查询两种模式。模糊查询不区分大小写。

此函数的核心在于它能够根据传入的 options 自动判断哪些字段需要进行精确匹配 (equal),哪些需要模糊匹配 (like)。例如,对于精确匹配,它会生成形如 ${alias}.${key} = :${key} 的条件;而对于模糊匹配,则生成 ${alias}.${key} LIKE LOWER(:${key}) 的条件,其中 % 符号用于实现模糊搜索。

如果 filterDto 中的 key 既不在 equal 也不在 like 里面,会被函数自动忽略,这样做的目的是,有些字段需要用更复杂的查询语句,这些查询需求不在此函数的开发目的中。

addQueryFilterConditions 函数并不直接返回任何值。其原因在于该函数的设计目的是直接操作传递给它的 SelectQueryBuilder 实例,通过.andWhere() 方法动态添加 WHERE 条件到查询构造器上,进而影响后续的数据库查询操作。

这样做的好处有:

  1. 链式调用的便利性 :TypeORM 的查询构建鼓励链式调用风格,addQueryFilterConditions 函数通过修改传入的 queryBuilder 对象,使得调用者可以在同一个链中继续添加其他查询条件或执行查询,保持代码的流畅性。
  2. 避免不必要的数据复制:由于函数直接操作原对象,避免了因返回新的查询构造器实例而导致的潜在性能开销或资源占用。
  3. 提升代码的可维护性和可读性:函数无返回值的设计使得调用者清楚地知道,此函数的作用是修改查询构造器的状态而非生成新的数据结构,这有助于理解函数的副作用和使用场景。
  4. 便于复用与扩展:不依赖返回值的设计使得该函数可以更容易地融入到不同的查询逻辑中,无论是单独使用还是与其他查询构建逻辑组合,都能保持良好的兼容性和灵活性。

代码

typescript 复制代码
import { isEmpty } from "radash"; // Radash 判空函数,当为 []、{}、""、null、undefined 的时候会返回 true
import { ObjectLiteral, type SelectQueryBuilder } from "typeorm";

interface IFilterOptions {
  equal?: string[];
  like?: string[];
}

/**
 * 为查询构造器添加过滤条件。
 * 此函数根据提供的过滤数据和选项动态地向查询构建器添加等值和模糊匹配条件。
 *
 * @param {string} alias 实体别名,在查询构造器中使用的表或实体别名。
 * @param {SelectQueryBuilder<any>} query 查询构造器实例,用于构建SQL查询。
 * @param {Record<string, any>} filterDto 过滤数据传输对象,包含需过滤的键值对。
 * @param {IFilterOptions} options 过滤选项,定义了哪些键应用等值匹配 (`equal`) 和哪些键应用模糊匹配 (`like`)。
 */
export const addQueryFilterConditions = (
  alias: string,
  query: SelectQueryBuilder<any>,
  filterDto: Record<string, any>,
  options: IFilterOptions
) => {
  console.log("run!");
  const willProcessKeys = [
    ...(options.equal || []),
    ...(options.like || []),
  ];

  willProcessKeys.forEach((key: string) => {
    if (options.equal?.includes(key)) {
      const value = filterDto[key];
      if (!isEmpty(value)) {
        console.log("2nd", key, value);
        query.andWhere(`${alias}.${key} = :${key}`, {
          [key]: value
        });
      }
    }

    if (options.like?.includes(key)) {
      const value = filterDto[key];
      console.log("3rd", key, value);
      if (!isEmpty(value)) {
        query.andWhere(`${alias}.${key} LIKE LOWER(:${key})`, {
          [key]: `%${value.toLowerCase()}%`
        });
      }
    }
  });
};

使用实例

定义一个 filterDto

typescript 复制代码
export class UserFilterDto {
  id?: string;
  userName?: string;
  status?: string;
}

Service 中使用

假设前端传来的 Body 是:

json 复制代码
{
  "id": "root",
  "userName": ""
}
typescript 复制代码
export class UserService {
  async findList(
    filterDto: UserFilterDto,
    queryParams: IBackendPaginatedQueryParamsWithMode
  ) {
    const queryBuilder = this.userRepository
      .createQueryBuilder("user")
      .select([
        "user.id",
        "user.userName",
        "user.email",
        "user.createdTime",
        "user.updatedTime",
        "user.status",
        "user.remark",
      ])
      .leftJoinAndSelect("user.roles", "roles");

    // 根据 userId 和 userName 模糊查询
    addQueryFilterConditions("user", queryBuilder, filterDto, {
      like: ["id", "userName"],
    });

    return queryBuilder.getMany();

    // `setQueryBuilderPagination`的回调函数,处理 roleIds 数组
    // const processQueryRecords = (records: Record<string, any>[]) => {
    //   return records.map((item: Record<string, any>) => {
    //     item.roleIds = item.roles.map((item) => item.id);
    //     return item;
    //   });
    // };

    // 分页公共方法,根据 queryBuilder、分页参数、回调函数。
    // return await setQueryBuilderPagination<User>(
    //   queryBuilder,
    //   queryParams,
    //   processQueryRecords
    // );
  }
}

那么 addQueryFilterConditions 就会根据 id = "root" 值来进行模糊查询,userName 值为空字符串,会自动跳过。

相关推荐
桃园码工7 小时前
15_TypeScript 对象 --[深入浅出 TypeScript 测试]
typescript·typescript 对象·typescript 测试
哟哟耶耶9 小时前
npm-npm install时rollbackFailedOptional: verb npm-session ce210dc17dd264aa报错
前端·npm·node.js
m0_748248779 小时前
最新最详细的配置Node.js环境教程
node.js
大梦百万秋9 小时前
从零开始搭建一个RESTful API(Node.js + Express)
node.js·restful·express
Libby博仙10 小时前
VUE3 常用的组件介绍
前端·javascript·vue.js·前端框架·npm·node.js
至少零下七度10 小时前
npm : 无法加载文件 D:\SoftFile\npm.ps1,因为在此系统上禁止运行脚本。
前端·npm·node.js
Libby博仙10 小时前
VUE3 VITE项目在 npm 中,关于 Vue 的常用命令有一些基础命令
前端·vue.js·npm·node.js
Libby博仙13 小时前
VUE3 一些常用的 npm 和 cnpm 命令,涵盖了修改源、清理缓存、修改 SSL 协议设置等内容。
缓存·npm·node.js·vue·ssl
来吧~14 小时前
webpack常见优化方法
前端·webpack·node.js