首页 后端技术 正文
  • 本文约3848字,阅读需19分钟
  • 48
  • 0

TypeORM连表查询方法详解

摘要

在 TypeORM 中实现连表查询,主要有两种方式:使用 QueryBuilder 构建灵活的查询,或者在 find 方法中指定关联关系。它们分别适用于不同的场景,下面这个表格汇总了它们的核心用法和区别: 特性维度 QueryBuilder 方式 find 选项方式 核心方法 createQueryBuilder(), leftJoinAndSelect()...

在 TypeORM 中实现连表查询,主要有两种方式:使用 QueryBuilder 构建灵活的查询,或者在 find 方法中指定关联关系。它们分别适用于不同的场景,下面这个表格汇总了它们的核心用法和区别:

特性维度 QueryBuilder 方式 find 选项方式
核心方法 createQueryBuilder(), leftJoinAndSelect(), innerJoinAndSelect() find(), findAndCount()
适用场景 复杂查询:多条件、模糊搜索、范围查询、分页 简单查询:快速获取实体及其关联对象
关联加载 在连接方法(如 leftJoinAndSelect)中显式指定 relations 选项中以字符串数组指定
灵活性 ,可自定义查询条件、选择字段、排序等 ,主要用于快速加载关联数据

🔧 使用 QueryBuilder 进行连表查询

QueryBuilder 功能强大,可以构建非常复杂的查询。基础的连接查询语法如下:

const results = await repository.createQueryBuilder('主表别名')
  .leftJoinAndSelect('主表.关联属性', '关联表别名')
  .getMany();
  • leftJoinAndSelect 是最常用的连接方式,它会返回左表(主表)的所有记录,即使右表(关联表)没有匹配的记录。如果只想返回在两个表中都匹配的记录,可以使用 innerJoinAndSelect
  • 你可以通过多个 .leftJoinAndSelect().innerJoinAndSelect() 链式调用,来连接多个表。

实际案例:假设你需要查询一个公司(company)及其所有的管理员用户(companyAdmins.user),并且需要对这些用户的名字进行模糊搜索,可以这样写:

const companies = await this.companyRepository.createQueryBuilder('company')
  .leftJoinAndSelect('company.companyAdmins', 'companyAdmin') // 首先连接到中间关系
  .leftJoinAndSelect('companyAdmin.user', 'user') // 再通过中间关系连接到目标用户
  .where('user.firstName LIKE :name', { name: `%${name}%` }) // 对关联表的字段进行条件过滤
  .getMany();

📖 使用 Find 选项进行连表查询

对于简单的关联查询,使用 find 方法并配置 relations 选项会更简洁。

const results = await someRepository.find({
  relations: ['关联属性1', '关联属性2']
});

实际案例:查询学生及其所在的教室和课程信息:

const students = await this.studentRepository.find({
  relations: ['classroom', 'course']
});

💡 处理特殊场景

  1. 多对多关系与自定义连接表字段
    当你的多对多关系连接表除了两个外键外,还有自定义字段(如 quantity, expirationDate),你需要将多对多关系拆解为两个一对多/多对一关系,并创建一个新的实体(如 ProductStash)来代表连接表。查询时需要先连接到这个中间实体,再连接到目标实体。
  2. 无外键关联的实体查询
    如果两个实体没有在 TypeORM 中定义关联关系,你仍然可以使用 QueryBuilder 进行连接,但需要使用更底层的 leftJoinAndMapOneleftJoinAndMapMany 方法,并手动指定连接条件。这适用于处理遗留数据库或特定场景。

⚠️ 注意事项

  • 在进行联表查询,尤其是多层级联表时,要注意性能问题。避免查询过多不需要的字段,并考虑在数据库中对关联字段建立索引。
  • 对于可能产生大量数据的查询,使用 .skip().take() 进行分页是非常必要的。

在TypeORM中使用 QueryBuilder进行连表查询时,您可以通过 .select()方法精确控制返回的字段,而不是使用 .getMany()获取整个实体。这里为您详细介绍几种常见场景下的字段选择方法。

💡 核心字段选择方法

.select()方法用于指定查询返回的字段。如果字段在实体定义中被标记为 { select: false },但在查询中又需要获取它,可以使用 .addSelect()方法。

📝 常见查询场景与字段控制

下面通过一个 UserProfile的简单例子,说明如何在不同情况下控制返回字段。

// 假设User实体中password字段设置了 @Column({ select: false })
const userRepository = getRepository(User);

// 1. 选择主表部分字段
const users = await userRepository.createQueryBuilder('user')
    .select(['user.id', 'user.username']) // 明确指定需要的字段
    .getMany();

// 2. 选择主表字段并加载关联表部分字段
const usersWithProfile = await userRepository.createQueryBuilder('user')
    .select(['user.id', 'user.username'])
    .leftJoinAndSelect('user.profile', 'profile') // 关联Profile实体
    .addSelect(['profile.avatar', 'profile.bio']) // 选择关联实体的特定字段
    .getMany();

// 3. 查询主表时包含默认隐藏的字段
const userWithPassword = await userRepository.createQueryBuilder('user')
    .select(['user.id', 'user.username'])
    .addSelect('user.password') // 特别添加被隐藏的password字段
    .where('user.id = :id', { id: userId })
    .getOne();

🔍 处理复杂关联与字段映射

对于更复杂的多级关联查询,或者需要自定义返回字段名的情况,可以这样处理:

// 4. 多级关联与条件查询
const complexResults = await this.componentRepo.createQueryBuilder('component')
    .leftJoinAndSelect('component.info', 'info')
    .leftJoinAndSelect('info.project', 'project')
    .select([
        'component.id',
        'component.componentCode',
        'info.projectName',
        'project.projectCode'
    ])
    .where('project.employeeIds LIKE :employeeId', { employeeId })
    .andWhere('info.platform LIKE :platform', { platform: `%${platform}%` })
    .getMany();

// 5. 字段名映射(如果需要)
// 注意:TypeORM的.select()方法本身不直接支持字段别名映射,通常依赖于实体定义。
// 但你可以通过getRawOne()或getRawMany()获取原始数据后处理,或者直接在查询中选择字段:
const rawResults = await userRepository.createQueryBuilder('user')
    .select('user.id', 'd_id')
    .addSelect('user.username', 'd_name')
    .getRawMany(); // 返回结果为 { d_id: 1, d_name: 'name' }

⚠️ 使用 .getRawMany()的注意事项

当你使用 .select()指定了特定字段,TypeORM将无法自动将结果转换为实体对象(即不能使用 .getMany()),因为返回的不是一个完整的实体。此时你需要使用 .getRawMany().getRawOne()来获取原始数据库结果。

  • .getMany():当查询结果能映射为一个完整的实体时使用。如果你通过 .leftJoinAndSelect加载了关联实体,并且主表字段足够构建实体,通常可用此方法。
  • .getRawMany():当查询结果包含自定义选择的字段、聚合函数结果,或无法直接映射到实体属性的数据时使用。返回的是普通对象数组。

🛠️ 实用技巧

  • 字段过滤:在定义实体时,对敏感字段(如 password)使用 @Column({ select: false }),可以避免在常规查询中意外泄露。在需要这些字段的特定场景(如登录验证),再使用 .addSelect()显式查询。
  • 性能优化:只选择必要的字段可以减少数据库的I/O开销和网络传输量,提升查询效率。

💎 总结

控制TypeORM连表查询返回字段的关键在于灵活运用 .select().addSelect(),并根据返回数据的结构选择正确的获取方法(.getMany().getRawMany())。

标签:nestjs
收藏



扫描二维码,在手机上阅读
    评论
    友情链接