TypeORM中不使用save()的批量插入最优方案及元数据关联后续插入问题咨询
TypeORM中不使用save()的批量插入最优方案及元数据关联后续插入问题咨询
我现在在TypeORM里用
repository.insert(...)做批量插入,想找个最佳方案把插入后的记录和它们对应的元数据关联起来,方便后续往其他表插入数据。具体场景是:我要批量插入一批Product实体,每个产品都有唯一的name字段;同时我维护了一个Map<string, { product, tags, locations }>,key是产品的name,value里包含了待插入的product实体、对应的tags和locations数据。现在的问题是,用insert()批量插入后,我拿不到返回的完整实体(或者说没法直接把返回的插入结果和原来Map里的元数据对应上),不知道该怎么高效关联,进而完成后续tags和locations的插入操作?
嘿,这个场景我之前做电商批量导入的时候刚好碰到过,给你几个实用的方案参考下:
首先得说清楚,repository.insert()之所以比save()快,核心就是它跳过了插入后查询回写完整实体的步骤,默认只会返回插入记录的主键ID(如果你的Product用的是自增主键的话)。结合你name唯一这个核心条件,有三个靠谱的方向可以选:
方案一:用返回的ID+唯一name做关联(最省心的通用方案)
这个方案逻辑简单、不容易踩坑,适合绝大多数场景:
- 第一步:先执行批量插入,拿到返回的主键标识符数组:
const insertResult = await productRepository.insert(productsToInsert); // insertResult.identifiers 里是每个插入记录的主键对象,比如 [{ id: 1 }, { id: 2 }, ...] - 第二步:把待插入的产品name收集起来,批量查询刚插入的完整Product记录:
const insertedNames = productsToInsert.map(p => p.name); const insertedProducts = await productRepository.find({ where: { name: In(insertedNames) } // 记得导入TypeORM的In操作符 }); - 第三步:遍历查询到的Product,从你的原Map里取出对应的tags和locations,就能批量插入到关联表了。
- 小提醒:虽然多了一次查询,但因为是批量操作,性能开销其实很小,中小规模的批量插入完全够用。
方案二:写原生SQL用RETURNING子句(追求极致性能的方案)
如果你的数据库支持RETURNING子句(比如PostgreSQL、MySQL 8.0及以上版本),可以直接用原生SQL插入后返回name和id,一步到位完成关联:
- 操作示例大概是这样:
const queryRunner = dataSource.createQueryRunner(); await queryRunner.connect(); // 构造批量插入的SQL,根据你的Product表实际字段调整 const values = productsToInsert.map(p => `('${p.name}', ${p.price})`).join(', '); const result = await queryRunner.query(` INSERT INTO product (name, price) VALUES ${values} RETURNING id, name; `); // 现在result里就是每个插入记录的id和name,直接和原Map关联 result.forEach(record => { const meta = yourOriginalMap.get(record.name); if (meta) { meta.productId = record.id; // 存好主键,后续插入关联表用 } }); await queryRunner.release(); - 优点:只需要一次数据库交互,性能拉满;缺点:依赖数据库特性,换数据库的话可能要调整SQL语句,适合大规模插入、对性能要求高的场景。
方案三:提前分配主键(适合非自增主键的场景)
如果你的Product主键不是自增的,而是可以自己生成的(比如UUID),那这个方案最顺畅:
- 插入前就给每个待插入的Product分配好唯一主键,比如用uuid库生成:
import { v4 as uuidv4 } from 'uuid'; // 假设你用uuid库生成ID const productsToInsert = Array.from(yourOriginalMap.values()).map(meta => { return { id: uuidv4(), name: meta.product.name, // 其他字段... }; }); - 执行批量插入后,因为主键是我们提前生成的,直接就能和原Map里的元数据关联,完全不用查数据库:
await productRepository.insert(productsToInsert); productsToInsert.forEach(product => { const meta = yourOriginalMap.get(product.name); if (meta) { meta.productId = product.id; } }); - 这个方案零额外查询,逻辑特别顺,但只适用于你能自主控制主键生成的场景,自增主键的话没法提前分配ID,就用不了这个方法。
备注:内容来源于stack exchange,提问作者aditya rawat
相关产品推荐
相关产品推荐

