# 如何在 SQLite 中进行参数化 CRUD

> 使用位置参数和 cursor 进行插入、读取、更新和删除，无需拼接 SQL。

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

# 如何在 SQLite 中进行参数化 CRUD

**目的。** 使用**位置参数**（`?`）执行 INSERT/UPDATE/DELETE 和 SELECT，避免字符串拼接（它会导致 SQL 注入和引号错误）。请注意 CrossFly 的两种索引约定。

> ⚠️ **索引。** 在**绑定**参数时，索引**从 1 开始**（`?` 第 1、2、… 个）。在从 cursor **读取**列时，索引**从 0 开始**（`getString(0)` = 第一列）。这是有意为之，并且与 SQLite 的原生 API 一致。

## 使用的 API
- `SqliteDb.execute(String sql, Object... params)` —— 准备语句、执行 `bindAll(params)`（bind **从 1 开始**）、执行单个 INSERT/UPDATE/DELETE，并返回**受影响的行数**（`int`）。类型会被推断：`Integer/Long/Short/Byte`→整数，`Float/Double`→实数，`Boolean`→0/1，`byte[]`→BLOB，`null`→NULL，否则 `toString()`→TEXT。
- `SqliteDb.query(String sql, Object... params)` —— 返回一个**已打开**的 `SqliteCursor`（bind 从 1 开始）。调用方**必须关闭**（使用 `try-with-resources`）。
- `SqliteCursor.next()` —— 前进到下一行；如果有行则返回 `true`。
- `SqliteCursor.getInt(col)/getLong/getDouble/getString/getBlob/isNull(col)` —— 类型化的 getter，列**从 0 开始**。
- `SqliteDb.queryLong(sql, defaultValue, params)` —— 快捷方式：第一行第一列作为 `long`，若无行则返回 `defaultValue`。
- `SqliteDb.queryString(sql, params)` —— 快捷方式：第一行第一列作为 `String`，若无行则返回 `null`。
- `SqliteDb.lastInsertRowid()` —— 最后一次 INSERT 的 rowid（`long`）。`SqliteDb.changes()` —— 最后一条语句更改的行数。

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

```java
package com.exemplo;

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

/** 可执行的主界面。将 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 person(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");

         // CREATE — 位置参数（? 第 1 = name，? 第 2 = age）
         int inserted = db.execute("INSERT INTO person(name, age) VALUES(?, ?)", "Alice", 30);
         long id = db.lastInsertRowid();            // 生成的 rowid
         System.out.println(inserted + " linha(s), id=" + id);

         // READ — cursor：列从 0 开始
         try (SqliteCursor c = db.query("SELECT name, age FROM person WHERE age > ?", 18))
         {
            while (c.next())
               System.out.println(c.getString(0) + " = " + c.getInt(1));   // col 0 = name，col 1 = age
         }

         // READ 标量 — 快捷方式
         long total = db.queryLong("SELECT count(*) FROM person", 0);
         String nome = db.queryString("SELECT name FROM person WHERE id = ?", id);

         // UPDATE — 返回受影响的行数
         int updated = db.execute("UPDATE person SET age = ? WHERE id = ?", 31, id);

         // DELETE
         int deleted = db.execute("DELETE FROM person WHERE id = ?", id);
      }
   }
}
```

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

## 最小片段
```java
// 已在源码验证：execute（bind 从 1 开始）、query + cursor（getters 从 0 开始）、queryLong/queryString
import dev.crossfly.sqlite.*;

try (SqliteDb db = SqliteDb.open("app.db"))
{
   db.exec("CREATE TABLE IF NOT EXISTS person(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");

   // CREATE — 位置参数（? 第 1 = name，? 第 2 = age）
   int inserted = db.execute("INSERT INTO person(name, age) VALUES(?, ?)", "Alice", 30);
   long id = db.lastInsertRowid();            // 生成的 rowid
   System.out.println(inserted + " linha(s), id=" + id);

   // READ — cursor：列从 0 开始
   try (SqliteCursor c = db.query("SELECT name, age FROM person WHERE age > ?", 18))
   {
      while (c.next())
         System.out.println(c.getString(0) + " = " + c.getInt(1));   // col 0 = name，col 1 = age
   }

   // READ 标量 — 快捷方式
   long total = db.queryLong("SELECT count(*) FROM person", 0);
   String nome = db.queryString("SELECT name FROM person WHERE id = ?", id);

   // UPDATE — 返回受影响的行数
   int updated = db.execute("UPDATE person SET age = ? WHERE id = ?", 31, id);

   // DELETE
   int deleted = db.execute("DELETE FROM person WHERE id = ?", id);
}
```

## 深入了解
- [SQLite 存储指南](/docs/data-and-services/storage-sqlite) · 开发指南
- 姊妹配方：[打开/创建数据库](/docs/cookbook/data/abrir-banco) · [事务与批量写入](/docs/cookbook/data/transacao-e-bulk) · [在 paint 线程之外使用](/docs/cookbook/data/banco-fora-da-paint-thread)
