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

AdonisJS中关联数据插入的事务回滚异常问题求助

问题根源与解决方案

首先拆解下你的代码里几个致命问题,正是这些问题导致事务无法正确回滚,还出现了日志里的报错:

1. 混用async/await与.then/.catch,错误被“吞掉”

你在Empresa.create和Contato.create里都加了内部的.catch,但这些catch只打印错误,没有抛出错误或者终止流程。这就导致即使创建Empresa或Contato失败,代码还是会一路执行到await trx.commit(),直接提交了事务,自然不会触发回滚。而且内部catch会阻止错误冒泡到外层的try/catch,你的rollback代码根本没机会执行。

2. forEach里用异步函数,导致事务提前提交

contatos.forEach(async contato => {...})这种写法有大问题:forEach不会等待内部的异步操作完成,会直接遍历完所有元素就继续执行后面的await trx.commit()。这意味着在Contato创建和attach操作还没完成的时候,事务已经被提交了,后面的操作再用这个事务就会报Transaction query already complete的错误,也就是你日志里的那个错误。

3. 未处理的Promise拒绝

当Contato.create失败时,novosContato是null,后面执行await empresa.contatos().attach(novosContato.id...)就会报错Cannot read property 'id' of null,而这个错误发生在forEach的异步函数里,没有被try/catch捕获,就会出现Adonis提示的未处理Promise拒绝。


修复后的控制器代码

把这些问题都修正后,代码应该是这样的:

'use strict'

const Database = use('Database')
const Empresa = use('App/Models/Empresa')
const Contato = use('App/Models/Contato')
const Sequence = use('App/Controllers/Http/SequenceController')

class EmpresaController {
  async customCreate ({ request, response, auth }) {
    const trx = await Database.beginTransaction()
    try {
      const { enderecos, contatos, ...data } = request.all()

      // 用可选链避免data.endereco/contato不存在的情况
      if (data.endereco?.logradouro !== '') {
        enderecos.push(data.endereco)
      }

      if (data.contato?.nome !== '') {
        contatos.push(data.contato)
      }

      const codigo = await Sequence.gerarNovoCodigoCliente(auth.user.grupo_id, trx)
      // 直接用await,去掉.then/.catch,让错误冒泡到外层try/catch
      const empresa = await Empresa.create({
        codigo: codigo,
        grupo_id: 1,
        tipo: data.tipo,
        origem: data.origem,
        nome_fantasia: data.nome_fantasia,
        razao_social: data.razao_social
      }, trx)

      // 用for...of代替forEach,确保每个异步操作完成后再执行下一个
      for (const contato of contatos) {
        const novosContato = await Contato.create({
          nome: contato.nome,
          dado: contato.dado,
          tipo_id: contato.tipo_contato.id
        }, trx)
        
        await empresa.contatos().attach(novosContato.id, null, trx)
      }

      await trx.commit()
      return response.ok(empresa)
    } catch (error) {
      await trx.rollback()
      console.error('事务回滚,错误信息:', error)
      return response.badRequest(error.message)
    }
  }
}

module.exports = EmpresaController

关键修复点说明

  • 移除所有内部.then/.catch:改用await配合外层的try/catch,任何一步出错都会直接进入catch块执行回滚,确保事务的原子性。
  • 用for...of替代forEach:for...of会等待循环体内的异步操作完成,再进行下一次循环,避免事务提前提交。
  • 添加可选链保护:比如data.endereco?.logradouro,防止data里没有endereco或contato字段时报错,增强代码健壮性。
  • 确保所有操作都绑定事务:你之前的代码里已经做到了,但要注意所有和数据库交互的操作都必须传入trx参数,不然操作会在事务之外执行(不过你的代码里都传了,这点没问题)。

现在再测试错误场景,比如故意让Contato的某个字段不符合约束,事务会正确回滚,empresas表也不会有残留数据了。

内容的提问来源于stack exchange,提问作者Luis Arruda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 08:48:11