Pular para o conteúdo
CrossFly é 100% open sourceSiga no GitHub
CrossFlyCrossFly
Começar

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)

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

// 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

Esta página foi útil?