如何在TypeORM批量插入时基于shortLink取消单条插入?
解决TypeORM批量插入时基于非主键字段校验并跳过重复项的问题
TypeORM的save()方法默认通过主键判断执行插入或更新,无法直接基于shortLink这类非主键字段做存在性校验;而在beforeInsert事件中抛出错误会终止整个批量插入操作,无法仅跳过重复项。以下是两种可行的实现方式:
方案一:提前预处理过滤重复项
在批量插入前,先为每个链接生成shortLink并检查是否已存在,仅对不存在的项执行插入操作。这种方式逻辑清晰,能避免后续事务处理的额外开销。
步骤1:为shortLink添加数据库唯一约束(兜底防并发)
首先在实体类中给shortLink字段添加唯一约束,防止并发场景下出现重复插入:
@Entity() export class QuickLink { @PrimaryGeneratedColumn() id: number; @Column() actualLink: string; @Column() @Unique() // 添加唯一约束 shortLink: string; }
步骤2:修改Service层的批量处理方法
async shortLinks(links: Array<string>): Promise<Array<QuickLinkDto>> { // 并行处理每个链接:生成shortLink并检查是否存在 const processedItems = await Promise.all(links.map(async (link) => { const shortLink = await getShortLink(link); const isExists = await this.quickLinkRepository.exists({ where: { shortLink } }); return isExists ? null : { actualLink: link, shortLink }; })); // 过滤掉已存在的项,只保留需要插入的 const needInsertItems = processedItems.filter(item => item !== null); // 批量插入有效项 const insertedItems = await this.quickLinkRepository.save(needInsertItems); // 若需返回所有请求链接的对应结果(包括已存在的),可合并查询结果 const allResults = await Promise.all(links.map(async (link) => { const shortLink = await getShortLink(link); const dbItem = await this.quickLinkRepository.findOne({ where: { shortLink } }); return { actualLink: dbItem.actualLink, shortLink: dbItem.shortLink }; })); return allResults; }
方案二:事务内逐个处理并捕获重复错误
通过创建事务,逐个处理每个链接的插入操作,捕获数据库唯一约束的错误并跳过重复项。这种方式依赖数据库约束,能有效避免并发竞态问题。
async shortLinks(links: Array<string>): Promise<Array<QuickLinkDto>> { const results: QuickLinkDto[] = []; const queryRunner = this.quickLinkRepository.manager.connection.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { for (const link of links) { const shortLink = await getShortLink(link); const quickLink = queryRunner.manager.create(QuickLink, { actualLink: link, shortLink }); try { // 尝试插入当前项 const savedItem = await queryRunner.manager.save(quickLink); results.push({ actualLink: savedItem.actualLink, shortLink: savedItem.shortLink }); } catch (err) { // 捕获唯一约束违反错误(PostgreSQL错误码为23505,MySQL为1062,需根据数据库调整) if (err.code === '23505') { // 查询已存在的项并加入结果 const existingItem = await queryRunner.manager.findOne(QuickLink, { where: { shortLink } }); results.push({ actualLink: existingItem.actualLink, shortLink: existingItem.shortLink }); } else { // 非重复错误,回滚事务并抛出 throw err; } } } await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); throw err; } finally { await queryRunner.release(); } return results; }
为什么beforeInsert无法实现需求?
TypeORM的批量插入操作中,beforeInsert是全局钩子,一旦在某个实体的钩子中抛出错误,整个批量插入流程会被终止,无法单独跳过当前重复项。因此需要通过上述两种方式绕开这个限制。
内容的提问来源于stack exchange,提问作者Sujeet
相关产品推荐
相关产品推荐

