TypeORM连表查询方法详解
在 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']
});
💡 处理特殊场景
- 多对多关系与自定义连接表字段
当你的多对多关系连接表除了两个外键外,还有自定义字段(如quantity
,expirationDate
),你需要将多对多关系拆解为两个一对多/多对一关系,并创建一个新的实体(如ProductStash
)来代表连接表。查询时需要先连接到这个中间实体,再连接到目标实体。 - 无外键关联的实体查询
如果两个实体没有在 TypeORM 中定义关联关系,你仍然可以使用QueryBuilder
进行连接,但需要使用更底层的leftJoinAndMapOne
或leftJoinAndMapMany
方法,并手动指定连接条件。这适用于处理遗留数据库或特定场景。
⚠️ 注意事项
- 在进行联表查询,尤其是多层级联表时,要注意性能问题。避免查询过多不需要的字段,并考虑在数据库中对关联字段建立索引。
- 对于可能产生大量数据的查询,使用
.skip()
和.take()
进行分页是非常必要的。
在TypeORM中使用 QueryBuilder
进行连表查询时,您可以通过 .select()
方法精确控制返回的字段,而不是使用 .getMany()
获取整个实体。这里为您详细介绍几种常见场景下的字段选择方法。
💡 核心字段选择方法
.select()
方法用于指定查询返回的字段。如果字段在实体定义中被标记为 { select: false }
,但在查询中又需要获取它,可以使用 .addSelect()
方法。
📝 常见查询场景与字段控制
下面通过一个 User
和 Profile
的简单例子,说明如何在不同情况下控制返回字段。
// 假设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()
)。