NestJS 中使用 TypeORM 查询包含指定分类的图书实体

4次阅读

NestJS 中使用 TypeORM 查询包含指定分类的图书实体

本文介绍如何在 Nestjs 中基于 TypeORM 的 QueryBuilder,精准筛选 category 字段(TypeORM 的 simple-Array 类型)中包含特定值的图书记录,并给出可直接集成到分页服务中的完整实现方案。

本文介绍如何在 nestjs 中基于 typeorm 的 querybuilder,精准筛选 `category` 字段(typeorm 的 `simple-array` 类型)中包含特定值的图书记录,并给出可直接集成到分页服务中的完整实现方案。

在 NestJS 项目中,当实体字段使用 @column({ type: “simple-array” })(如 category: String[])时,TypeORM 会将其序列化为数据库中的字符串数组(例如 postgresql 的 text[] 或 mysqljson/TEXT 字符串)。注意:simple-array 并不支持原生数组操作符(如 @> 或 CONTAINS),因此不能直接用 IN 判断数组元素是否存在——上述答案中的 IN (:…category) 写法在多数数据库下无法正确匹配数组内元素,属于常见误区。

✅ 正确做法是利用数据库对序列化数组字符串的文本匹配能力,结合 TypeORM 提供的安全参数化查询方式。以下是推荐的、跨数据库兼容性良好的解决方案:

✅ 推荐方案:使用 LIKE + JSON/数组字符串格式化(适用于 MySQL/PostgreSQL/sqlite

由于 simple-array 在数据库中存储为带引号的 JSON 风格字符串(如 [“”test1″”,””test2″”,””test3″”] 或 “{“test1″,”test2″,”test3”}”,具体取决于数据库和配置),我们应通过 LIKE 模糊匹配确保目标分类被完整、独立地包含在数组中。

public async getBooksByCategory(   category: string,   page: number = 1, ): Promise<PageDto<BookEntity>> {   const queryBuilder = this.bookRepository.createQueryBuilder('book_entity');    // 安全拼接:匹配 '"category"' 作为独立元素(防误匹配如 'test' 匹配 'test2')   queryBuilder.where('book_entity.category LIKE :pattern', {     pattern: `%"${category}"%`,   });    queryBuilder.take(10).skip((page - 1) * 10);    const [entities, itemCount] = await queryBuilder.getManyAndCount();    return new PageDto<BookEntity>(entities, itemCount, page); }

? 原理说明:simple-array 序列化后,每个元素均被双引号包裹并以逗号分隔(如 [“”fiction””,””sci-fi””]),因此 %”${category}”% 可精准定位完整分类项,避免子串误匹配(如 “fic” 不会匹配 “fiction”)。

⚠️ 注意事项与最佳实践

  • 不要使用 IN 或 FIND_IN_SET 直接操作 simple-array 字段:IN 用于标量列,对序列化字符串无效;FIND_IN_SET(MySQL)仅适用于逗号分隔纯字符串(无引号),而 simple-array 默认含转义引号,易导致匹配失败。
  • 更健壮的替代方案:改用 jsonb(PostgreSQL)或 @OneToMany 关系表
    若需高频、高性能的数组查询,建议重构为:
    • PostgreSQL:将 category 改为 @Column({ type: ‘jsonb’ }),配合 @ContainedBy / @Contains 查询;
    • 或建立 book_category 关联表,实现真正规范化查询(支持索引、JOIN、聚合等)。
  • 前端传参需校验:确保 category 为非空、合法字符串,避免 SQL 注入风险(当前 LIKE + 参数化已防御,但仍建议白名单校验)。
  • 分页逻辑优化:使用 getManyAndCount() 替代 getRawAndEntities(),更简洁且类型安全。

✅ 完整服务方法示例(含错误处理与类型提示)

import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, SelectQueryBuilder } from 'typeorm'; import { BookEntity } from './book.entity'; import { PageDto } from '../common/dto/page.dto';  @Injectable() export class BookService {   constructor(     @InjectRepository(BookEntity)     private readonly bookRepository: Repository<BookEntity>,   ) {}    async getBooksByCategory(     category: string,     page: number = 1,   ): Promise<PageDto<BookEntity>> {     if (!category?.trim()) {       throw new NotFoundException('Category cannot be empty');     }      const queryBuilder = this.bookRepository       .createQueryBuilder('book_entity')       .where('book_entity.category LIKE :pattern', {         pattern: `%"${category.trim()}"%`,       });      queryBuilder.take(10).skip((page - 1) * 10);      try {       const [entities, itemCount] = await queryBuilder.getManyAndCount();       return new PageDto<BookEntity>(entities, itemCount, page);     } catch (error) {       console.error('Failed to fetch books by category:', error);       throw error;     }   } }

通过以上实现,你即可在 NestJS 中稳定、安全、高效地按分类筛选图书,同时保持代码可维护性与扩展性。如业务规模增长,建议逐步迁移至关系型分类设计以获得更强查询能力。

text=ZqhQzanResources