如何在TypeORM中结合QueryBuilder关联外部数据库数据
问题描述
我定义了如下的FileEntity实体:
@Entity() export class FileEntity { @PrimaryGeneratedColumn('uuid') id: string; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; @DeleteDateColumn() deletedAt: Date; @Column({ default: null }) originalname: string; @Column({ default: null }) mimetype: string; @Column({ default: null }) url: string; @ManyToOne(() => UserEntity, (user) => user.files) user: UserEntity; @Column({ default: null }) size: number; videoMetadata: any; }
其中videoMetadata(如视频时长、分辨率等)存储在外部数据库(视频提供商)中。是否可以将外部提供商的该数据映射到我的videoMetadata属性上?以下是我用于获取文件列表的findAll方法,我希望能通过leftJoin来关联外部表:
findAll(data: InputFilePaginationDto) { const service = this; function getFilesQuery(skip?: 'mimetype') { const query = service._fileRepository.createQueryBuilder('file'); query.leftJoinAndSelect('file.user', 'user'); if (data.userId) { query.andWhere('user.id = :userId', { userId: data.userId }); } if (data.minSize) { query.andWhere('file.size >= :minSize', { minSize: data.minSize }); } if (data.maxSize) { query.andWhere('file.size <= :maxSize', { maxSize: data.maxSize }); } if (data.mimetypes && skip !== 'mimetype') { query.andWhere('file.mimetype IN (:...mimetypes)', { mimetypes: data.mimetypes }); } return query; } const query = getFilesQuery(); return pagination( data, query, ['file.originalname'], { default: 'file.updatedAt', createdAt: 'file.createdAt', updatedAt: 'file.updatedAt', originalname: 'file.originalname', }, ); }
解决方案
首先明确:TypeORM的QueryBuilder无法直接用leftJoin关联外部数据库的表,因为它只能操作当前连接的数据库实例。但可以通过两种方式实现外部元数据到videoMetadata属性的映射:
方法1:查询本地数据后批量拉取外部元数据
这是最通用的方案,逻辑简单易维护,适合大多数场景:
- 先执行原有逻辑获取本地文件的分页数据
- 筛选出需要获取元数据的视频文件(通过mimetype判断)
- 调用视频提供商的批量API获取元数据
- 将元数据逐个赋值给对应文件的
videoMetadata属性
修改后的findAll方法示例:
async findAll(data: InputFilePaginationDto) { const service = this; function getFilesQuery(skip?: 'mimetype') { const query = service._fileRepository.createQueryBuilder('file'); query.leftJoinAndSelect('file.user', 'user'); if (data.userId) { query.andWhere('user.id = :userId', { userId: data.userId }); } if (data.minSize) { query.andWhere('file.size >= :minSize', { minSize: data.minSize }); } if (data.maxSize) { query.andWhere('file.size <= :maxSize', { maxSize: data.maxSize }); } if (data.mimetypes && skip !== 'mimetype') { query.andWhere('file.mimetype IN (:...mimetypes)', { mimetypes: data.mimetypes }); } return query; } // 获取分页后的文件列表 const paginationResult = await pagination( data, getFilesQuery(), ['file.originalname'], { default: 'file.updatedAt', createdAt: 'file.createdAt', updatedAt: 'file.updatedAt', originalname: 'file.originalname', }, ); // 筛选出视频文件 const videoFiles = paginationResult.items.filter(file => file.mimetype?.startsWith('video/')); if (videoFiles.length === 0) return paginationResult; // 提取文件标识(这里假设用url和外部库关联) const fileIdentifiers = videoFiles.map(file => file.url); // 调用外部API批量获取元数据,替换成实际的API调用逻辑 const metadataMap = await yourVideoProvider.getBatchMetadata(fileIdentifiers); // 给文件赋值元数据 videoFiles.forEach(file => { file.videoMetadata = metadataMap[file.url]; }); return paginationResult; }
方法2:创建跨库数据库视图(仅支持特定数据库环境)
如果你的主数据库和视频提供商的数据库支持跨库访问(比如同集群、支持联邦查询),可以用数据库视图实现关联:
- 在主数据库中创建视图,关联本地
file表和外部的视频元数据表(用唯一标识如url或id做关联键) - 创建对应视图的实体类,或者扩展原有
FileEntity,之后直接查询该视图获取包含元数据的结果
示例(假设外部表名为external_video_metadata,关联键是file_url):
@ViewEntity({ expression: ` SELECT f.*, evm.duration, evm.resolution, evm.bitrate FROM file f LEFT JOIN external_video_metadata evm ON f.url = evm.file_url ` }) export class FileWithMetadataView extends FileEntity { duration: number; resolution: string; bitrate: number; }
之后查询这个视图实体,就能直接拿到包含元数据的结果,你可以把这些字段合并到videoMetadata里,或者直接使用视图中的字段。
注意事项
- 不要尝试用TypeORM的QueryBuilder直接关联外部库,它只能操作当前连接的数据库
- 优先使用批量API获取元数据,避免逐个查询导致性能问题
- 如果外部API有调用频率限制,要做好缓存或限流处理
内容的提问来源于stack exchange,提问作者Korer
相关产品推荐
相关产品推荐

