You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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:查询本地数据后批量拉取外部元数据

这是最通用的方案,逻辑简单易维护,适合大多数场景:

  1. 先执行原有逻辑获取本地文件的分页数据
  2. 筛选出需要获取元数据的视频文件(通过mimetype判断)
  3. 调用视频提供商的批量API获取元数据
  4. 将元数据逐个赋值给对应文件的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:创建跨库数据库视图(仅支持特定数据库环境)

如果你的主数据库和视频提供商的数据库支持跨库访问(比如同集群、支持联邦查询),可以用数据库视图实现关联:

  1. 在主数据库中创建视图,关联本地file表和外部的视频元数据表(用唯一标识如url或id做关联键)
  2. 创建对应视图的实体类,或者扩展原有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 05:38:18