# Como escrever em massa com transação e bulkInsert

> Inserir milhares de linhas rápido — transação, statement reusado com reset() ou bulkInsert nativo.

Source: https://crossfly.dev/pt/docs/cookbook/data/transacao-e-bulk — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Como escrever em massa com transação e bulkInsert

**Propósito.** Inserir/atualizar muitas linhas com alta performance. Sem transação, cada INSERT vira um commit em disco (lento). Há três níveis: (1) envolver tudo em `transaction(...)`, (2) reusar um `SqliteStmt` com `reset()`, (3) usar `bulkInsert`, que faz o loop bind/step/reset em código nativo (mais rápido).

## APIs usadas
- `SqliteDb.transaction(Runnable body)` — roda `body` dentro de `BEGIN`/`COMMIT`; faz **`ROLLBACK` automático** se `body` lançar `RuntimeException` (e relança). Forma recomendada para atomicidade.
- `SqliteDb.begin()` / `commit()` / `rollback()` — controle manual da transação (equivalem a `exec("BEGIN"/"COMMIT"/"ROLLBACK")`). Prefira `transaction(...)` para não esquecer o rollback.
- `SqliteDb.prepare(String sql)` — prepara um `SqliteStmt` reusável (compila uma vez). **Você deve fechá-lo** (`try-with-resources`).
- `SqliteStmt.bindInt/bindLong/bindDouble/bindString/bindBlob/bindNull(idx, v)` e `bindAll(Object...)` — bind **1-based**; retornam `this` (fluente). `step()` executa; `reset()` prepara para a próxima execução (mantém os binds; use `clearBindings()` se quiser zerar).
- `SqliteDb.bulkInsert(String sql, List<Object[]> rows)` — prepara `sql` uma vez e executa para cada `Object[]` **dentro de uma única transação**, em loop nativo. Retorna o nº de linhas processadas. Cada `Object[]` tem um elemento por parâmetro `?`; tipos aceitos: `Long/Integer`, `Double/Float`, `String`, `byte[]`, `null`. **Os tipos de coluna são inferidos da PRIMEIRA linha** — mantenha as linhas homogêneas. Aliases: `bulkUpdate`, `bulkDelete`.

> **Por que reusar o statement?** `prepare` compila o SQL; repetir `prepare` a cada linha recompila. Reusar com `reset()` compila uma vez e apenas re-binda — muito mais rápido em loops grandes.

## Exemplo completo (copiar, colar e rodar)

```java
package com.exemplo;

import java.util.*;                          // List, ArrayList

import dev.crossfly.ui.*;                     // Window
import dev.crossfly.ui.layout.*;             // VerticalLayout
import dev.crossfly.sqlite.*;                // SqliteDb, SqliteStmt

/** Tela principal executável. Aponte o launcher (classname) e rode: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      try (SqliteDb db = SqliteDb.open("app.db"))
      {
         db.exec("CREATE TABLE IF NOT EXISTS t(a INTEGER, b TEXT)");

         // Opção A — transação + statement reusado (reset entre execuções)
         db.transaction(() ->
         {
            try (SqliteStmt st = db.prepare("INSERT INTO t(a, b) VALUES(?, ?)"))
            {
               for (int i = 0; i < 10_000; i++)
               {
                  st.bindInt(1, i)            // idx 1-based
                    .bindString(2, "row" + i)
                    .step();
                  st.reset();                 // pronto para a próxima linha
               }
            }
         }); // commit automático; rollback automático se lançar

         // Opção B — bulkInsert nativo (o mais rápido para volumes grandes)
         List<Object[]> rows = new ArrayList<>();
         for (int i = 0; i < 10_000; i++)
            rows.add(new Object[]{ i, "row" + i });   // tipos inferidos da 1ª linha
         int n = db.bulkInsert("INSERT INTO t(a, b) VALUES(?, ?)", rows);
         System.out.println(n + " linhas inseridas");
      }
   }
}
```

> O trecho abaixo mostra **só o miolo**. Para rodar, coloque-o no esqueleto `MainWindow` acima.

## Trecho mínimo
```java
// verificado na fonte: transaction, prepare + bind (1-based) + step + reset, bulkInsert
import dev.crossfly.sqlite.*;
import java.util.*;

try (SqliteDb db = SqliteDb.open("app.db"))
{
   db.exec("CREATE TABLE IF NOT EXISTS t(a INTEGER, b TEXT)");

   // Opção A — transação + statement reusado (reset entre execuções)
   db.transaction(() ->
   {
      try (SqliteStmt st = db.prepare("INSERT INTO t(a, b) VALUES(?, ?)"))
      {
         for (int i = 0; i < 10_000; i++)
         {
            st.bindInt(1, i)            // idx 1-based
              .bindString(2, "row" + i)
              .step();
            st.reset();                 // pronto para a próxima linha
         }
      }
   }); // commit automático; rollback automático se lançar

   // Opção B — bulkInsert nativo (o mais rápido para volumes grandes)
   List<Object[]> rows = new ArrayList<>();
   for (int i = 0; i < 10_000; i++)
      rows.add(new Object[]{ i, "row" + i });   // tipos inferidos da 1ª linha
   int n = db.bulkInsert("INSERT INTO t(a, b) VALUES(?, ?)", rows);
   System.out.println(n + " linhas inseridas");
}
```

## Aprofunde-se
- [Trabalho em background](/docs/data-and-services/background-work) · [Performance e listas grandes](/docs/best-practices/performance-e-listas-grandes)
- Receitas irmãs: [CRUD parametrizado](/docs/cookbook/data/crud) · [Uso fora da paint thread](/docs/cookbook/data/banco-fora-da-paint-thread)
