跳到内容
CrossFly 是 100% 开源在 GitHub 关注我们
CrossFlyCrossFly
开始使用

本页面由葡萄牙语机器翻译,可能存在错误。葡萄牙语版本为准。

如何使用事务和 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() 重用只编译一次并仅重新绑定——在大循环中快得多。

完整示例(复制、粘贴并运行)

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 骨架中。

最小片段

// 已在源码验证: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");
}

深入了解

这个页面有帮助吗?