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

