Docs›Banco de dados›Transações
Banco de dados

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/catch manual pra rollback — se o closure lançar qualquer Throwable, 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 begin e o commit que não cabe num único closure (ex.: decidir o rollback condicionalmente sem lançar exceção). Pra tudo mais, prefira transaction().

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étodoDescriçã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