Transações
DB::transaction(), retry em deadlock, multi-database, savepoints.
Transações no MAD são as transações nativas do Laravel — DB::transaction()
(recomendado) ou o par manual beginTransaction()/commit()/rollBack().
Diferente do ORM legado, escrita no banco não exige uma transação aberta
pra funcionar — mas qualquer operação composta (mais de um INSERT/UPDATE que precisam ser
atômicos) deve estar dentro de uma.
Padrão recomendado — DB::transaction()
Commit automático no fim do closure, rollback automático se qualquer exceção for lançada dentro dele:
use Illuminate\Support\Facades\DB;
DB::connection('business')->transaction(function () {
$produto = Produto::find($id);
$produto->nome = 'Camiseta P';
$produto->save();
});
Exemplo real do projeto (App\Models\Ged\Config::set()):
public static function set(string $key, string $value): void
{
DB::connection('ged')->transaction(function () use ($key, $value) {
self::updateOrCreate(['key' => $key], ['value' => $value]);
});
}
Não precisa de
try/catchmanual pra rollback — se o closure lançar qualquerThrowable,DB::transaction()já faz rollback sozinho e relança a exceção pra cima. Capture no nível que for tratar o erro (controller/handler), não dentro da transação.
public function onSave(): MadResponse
{
try {
DB::connection('business')->transaction(function () {
// ... operações ...
});
return MadToast::success('Salvo!');
} catch (\Throwable $e) {
return MadMessage::error('Erro', $e->getMessage());
}
}
Retry automático em deadlock
O segundo argumento de transaction() é o número de tentativas — útil pra
deadlocks transitórios em cargas concorrentes (o Laravel detecta Deadlock found
/ Lock wait timeout e re-executa o closure):
DB::connection('business')->transaction(function () {
// ...
}, attempts: 3);
Controle manual
DB::connection('business')->beginTransaction();
try {
$produto = Produto::find($id);
$produto->save();
DB::connection('business')->commit();
} catch (\Throwable $e) {
DB::connection('business')->rollBack();
throw $e;
}
Use o modo manual só quando precisa de lógica entre o
begine ocommitque não cabe num único closure (ex.: decidir o rollback condicionalmente sem lançar exceção). Pra tudo mais, prefiratransaction().
Multi-database
O MAD mantém 6 conexões lógicas simultâneas (iam, log,
business, comm, ged, ai). Cada model
já sabe a sua via protected $connection — você só precisa qualificar a
conexão quando usa DB::transaction() ou DB::connection()
diretamente:
DB::connection('business')->transaction(function () {
Produto::create([...]); // conexão 'business', via $connection do model
});
DB::connection('iam')->transaction(function () {
User::create([...]); // conexão 'iam'
});
Cada
DB::connection($nome)->transaction()abre/fecha uma transação independente naquela conexão — não há transação distribuída entre conexões diferentes (cada uma commita/rollback por conta própria).
Transações aninhadas (savepoints)
Diferente do ORM legado, o Laravel suporta transações aninhadas
nativamente — chamadas de transaction() dentro de outra usam
SAVEPOINT/ROLLBACK TO SAVEPOINT automaticamente (suportado por
todos os drivers usados pelo MAD: SQLite, MySQL, PostgreSQL, SQL Server):
DB::connection('business')->transaction(function () {
Produto::create([...]);
DB::connection('business')->transaction(function () {
// savepoint — se isso falhar, só este bloco é desfeito,
// a criação do Produto acima permanece
ItemEstoque::create([...]);
});
});
API
| Método | Descrição |
|---|---|
DB::connection($nome)->transaction($closure, $attempts = 1) | Abre, executa, commita; rollback automático em exceção |
DB::connection($nome)->beginTransaction() | Abre transação manual |
DB::connection($nome)->commit() | Commita a transação atual |
DB::connection($nome)->rollBack() | Desfaz a transação atual |
DB::connection($nome)->transactionLevel() | Profundidade de aninhamento atual (0 = nenhuma aberta) |
NUNCA fazer
// ❌ ERRADO — commit manual sem garantir rollback em exceção
DB::connection('business')->beginTransaction();
$produto->save(); // se lançar, a transação fica pendurada
DB::connection('business')->commit();
// ✅ CERTO — try/catch com rollback, ou simplesmente use transaction()
try {
DB::connection('business')->beginTransaction();
$produto->save();
DB::connection('business')->commit();
} catch (\Throwable $e) {
DB::connection('business')->rollBack();
throw $e;
}
// ❌ ERRADO — efeitos colaterais não-transacionais (email, webhook, arquivo) dentro do closure
DB::connection('business')->transaction(function () use ($pedido) {
$pedido->save();
Mail::to($pedido->cliente->email)->send(new PedidoConfirmado($pedido));
// se o e-mail falhar, a transação inteira sobe como exceção e desfaz o save
});
// ✅ CERTO — side effects fora da transação, ou despachados como job
DB::connection('business')->transaction(function () use ($pedido) {
$pedido->save();
});
Mail::to($pedido->cliente->email)->send(new PedidoConfirmado($pedido));
Próximos
- Models Eloquent — CRUD básico.
- Operações em massa — update/delete em lote.
- Callbacks — eventos disparados dentro do save.