# 如何使用事务和 bulkInsert 进行批量写入

> 快速插入数千行——事务、用 reset() 重用 statement，或原生 bulkInsert。

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

# 如何使用事务和 bulkInsert 进行批量写入

**目的。** 以高性能插入/更新大量行。没有事务时，每个 INSERT 都会变成一次磁盘提交（很慢）。有三个层级：(1) 用 `transaction(...)` 包裹所有操作，(2) 用 `reset()` 重用一个 `SqliteStmt`，(3) 使用 `bulkInsert`，它在原生代码中执行 bind/step/reset 循环（最快）。

## 使用的 API
- `SqliteDb.transaction(Runnable body)` —— 在 `BEGIN`/`COMMIT` 内运行 `body`；如果 `body` 抛出 `RuntimeException`，则**自动 `ROLLBACK`**（并重新抛出）。是保证原子性的推荐方式。
- `SqliteDb.begin()` / `commit()` / `rollback()` —— 手动控制事务（等同于 `exec("BEGIN"/"COMMIT"/"ROLLBACK")`）。优先使用 `transaction(...)` 以免忘记回滚。
- `SqliteDb.prepare(String sql)` —— 准备一个可重用的 `SqliteStmt`（编译一次）。**你必须关闭它**（`try-with-resources`）。
- `SqliteStmt.bindInt/bindLong/bindDouble/bindString/bindBlob/bindNull(idx, v)` 和 `bindAll(Object...)` —— bind **从 1 开始**；返回 `this`（流式）。`step()` 执行；`reset()` 为下一次执行做准备（保留绑定；若要清零请使用 `clearBindings()`）。
- `SqliteDb.bulkInsert(String sql, List<Object[]> rows)` —— 将 `sql` 准备一次，并**在单个事务内**为每个 `Object[]` 执行，采用原生循环。返回已处理的行数。每个 `Object[]` 的每个参数 `?` 对应一个元素；接受的类型：`Long/Integer`、`Double/Float`、`String`、`byte[]`、`null`。**列类型从第一行推断** —— 请保持各行类型一致。别名：`bulkUpdate`、`bulkDelete`。

> **为什么要重用 statement？** `prepare` 会编译 SQL；每一行都重复 `prepare` 会重新编译。用 `reset()` 重用只编译一次并仅重新绑定——在大循环中快得多。

## 完整示例（复制、粘贴并运行）

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

/** 可执行的主界面。将 launcher 指向此类（classname）并运行：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)");

         // 方案 A — 事务 + 复用 statement（每次执行之间 reset）
         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 开始
                    .bindString(2, "row" + i)
                    .step();
                  st.reset();                 // 为下一行做好准备
               }
            }
         }); // 自动 commit；抛异常则自动 rollback

         // 方案 B — 原生 bulkInsert（大批量数据最快）
         List<Object[]> rows = new ArrayList<>();
         for (int i = 0; i < 10_000; i++)
            rows.add(new Object[]{ i, "row" + i });   // 类型从第 1 行推断
         int n = db.bulkInsert("INSERT INTO t(a, b) VALUES(?, ?)", rows);
         System.out.println(n + " linhas inseridas");
      }
   }
}
```

> 下面的代码片段仅展示**核心部分**。要运行它，请将其放入上面的 `MainWindow` 骨架中。

## 最小片段
```java
// 已在源码验证：transaction、prepare + bind（从 1 开始）+ 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)");

   // 方案 A — 事务 + 复用 statement（每次执行之间 reset）
   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 开始
              .bindString(2, "row" + i)
              .step();
            st.reset();                 // 为下一行做好准备
         }
      }
   }); // 自动 commit；抛异常则自动 rollback

   // 方案 B — 原生 bulkInsert（大批量数据最快）
   List<Object[]> rows = new ArrayList<>();
   for (int i = 0; i < 10_000; i++)
      rows.add(new Object[]{ i, "row" + i });   // 类型从第 1 行推断
   int n = db.bulkInsert("INSERT INTO t(a, b) VALUES(?, ?)", rows);
   System.out.println(n + " linhas inseridas");
}
```

## 深入了解
- [后台工作](/docs/data-and-services/background-work) · [性能与大列表](/docs/best-practices/performance-e-listas-grandes)
- 姊妹配方：[参数化 CRUD](/docs/cookbook/data/crud) · [在 paint 线程之外使用](/docs/cookbook/data/banco-fora-da-paint-thread)
