如何使用Knex事务实现一对多关系的记录插入?
如何使用Knex事务实现一对多关系的记录插入?
我来帮你梳理下这个问题,你的思路方向完全是对的,但代码里有个关键的小问题需要调整,咱们一步步来解决:
首先说你现有代码里的坑:input.genres.forEach(async (g) => { ... })这里存在异步操作的时序问题——forEach不会等待内部的异步任务完成,这会导致事务可能在所有genre关联操作还没做完的时候就提前提交了,轻则出现部分关联记录缺失,重则数据完全不一致。
接下来给你修正后的实现方案,同时解释每个步骤的必要性:
核心思路
- 在事务内先插入书籍,拿到完整的书籍对象
- 对每个genre执行「查找或创建」操作,必须确保这些操作也在当前事务内,否则事务回滚时新建的genre不会被删除
- 批量插入书籍与genre的关联记录(比逐个插入更高效)
- 所有操作完成后返回书籍对象;任何一步出错,事务自动回滚,所有已插入的数据都会被撤销
修正后的代码
async create(input: { name: string; user: User; genres: string[]; }): Promise<Book | undefined> { try { return await knex.transaction(async (trx) => { // 1. 插入书籍并返回完整的书籍实体 const [insertedBook] = await trx<Book>("books").insert( { name: input.name, user_id: input.user.id, }, ["id", "name", "user_id", "created_at", "updated_at"] ); // 2. 批量处理所有genre:确保每个操作都在事务内完成 const genreIds = await Promise.all( input.genres.map(async (genreName) => { // 注意:这里要把事务对象trx传给findOrCreate方法 const { id } = await this.gs.findOrCreate(genreName, trx); return id; }) ); // 3. 批量生成关联记录并插入中间表,提升性能 const bookGenreRelations = genreIds.map(genreId => ({ book_id: insertedBook.id, genre_id: genreId, })); await trx<BookGenre>("book_genres").insert(bookGenreRelations); // (可选)如果需要返回包含genre信息的完整书籍,可以在这里做关联查询 // const fullBook = await trx("books") // .where("id", insertedBook.id) // .leftJoin("book_genres", "books.id", "book_genres.book_id") // .leftJoin("genres", "book_genres.genre_id", "genres.id") // .select("books.*", trx.raw("array_agg(genres.name) as genres")) // .groupBy("books.id") // .first(); return insertedBook; }); } catch (error) { console.error("创建书籍及关联分类失败", error); return undefined; } }
配套调整findOrCreate方法
你需要把findOrCreate改成支持传入事务对象的版本,这样所有操作都在同一个事务上下文里:
async findOrCreate(genreName: string, trx: Knex.Transaction): Promise<Genre> { // 先尝试查找已有分类 let genre = await trx<Genre>("genres").where("name", genreName).first(); if (!genre) { // 不存在则插入并返回新分类 [genre] = await trx<Genre>("genres").insert({ name: genreName }, ["id", "name"]); } return genre; }
关键注意点
- 用
Promise.all替代forEach处理异步操作:既保证所有genre操作完成后再继续,又能并行处理提升效率 - 所有数据库操作必须使用事务对象
trx:如果用全局的knex实例执行操作,那部分操作会脱离事务,回滚时不会被撤销 - 批量插入关联记录:当genre数量较多时,批量插入比循环逐个插入能大幅减少数据库请求次数,提升性能
备注:内容来源于stack exchange,提问作者bambi
相关产品推荐
相关产品推荐

