# 如何在 paint 线程之外访问数据库

> 在工作线程中运行 SQLite 查询，并使用 Component.onUI 返回 UI。

Source: https://crossfly.dev/zh/docs/cookbook/data/banco-fora-da-paint-thread — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 如何在 paint 线程之外访问数据库

**目的。** 繁重的查询（扫描、批量操作、迁移）如果在 CrossFly 唯一的 paint 线程上运行，会导致 UI 卡死。规则是：**在工作线程中执行数据库 I/O**，并且**仅通过 `Component.onUI(...)` 更新 UI**，它会将代码重新转发到 UI 线程。

## 使用的 API
- `new Thread(Runnable).start()` —— 创建一个工作线程用于访问数据库。所有 `SqliteDb`/`query`/`bulkInsert` 都在这里运行，远离 paint 线程。
- `Component.onUI(Runnable r)` —— 将 `r` 排入队列，使其在 **UI 线程**上运行（内部为 `Gdx.app.postRunnable`）。**所有**对组件的操作（设置文本、向列表添加项、`repaint`）都必须在此回调内发生。
- `SqliteDb` / `SqliteCursor` —— 在其**自身线程内**打开并**关闭**（通过 `try-with-resources`）。不要在线程之间共享连接；在你使用的地方打开你自己的连接。

> **线程处理黄金法则。** 读/写数据库：工作线程。触碰 UI 组件：`Component.onUI`。绝不能反过来——在 UI 线程之外访问组件会造成损坏；用 I/O 阻塞 paint 线程会使应用卡死。

> **离开线程前先收集。** `SqliteCursor` 是 `AutoCloseable` 且与其连接绑定。**不要**把 cursor 传入 `onUI`。在工作线程中将数据物化（例如放入一个 `List`），并只把准备好的列表传给 UI。

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

```java
package com.exemplo;

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

import dev.crossfly.ui.*;                     // Window, Component
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());

      new Thread(() ->
      {
         // --- 工作 thread：数据库 I/O ---
         List<String> nomes = new ArrayList<>();
         try (SqliteDb db = SqliteDb.open("app.db");
              SqliteCursor c = db.query("SELECT name FROM person ORDER BY name"))
         {
            while (c.next())
               nomes.add(c.getString(0));   // 在离开 thread 之前采集
         }

         // --- 仅为更新组件而回到 UI ---
         Component.onUI(() ->
         {
            // 此处可安全操作组件（设置列表、repaint 等）
            preencheLista(nomes);
         });
      }).start();
   }

   /** Stub：在此用加载的数据更新你的 UI 组件。 */
   private void preencheLista(List<String> nomes)
   {
      // 例如：在列表中构建项、设置文本、repaint()...
   }
}
```

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

## 最小片段
```java
// 已在源码核实：Component.onUI (Gdx.app.postRunnable)，SqliteDb.query/cursor 从 0 开始
import dev.crossfly.sqlite.*;
import dev.crossfly.ui.Component;
import java.util.*;

new Thread(() ->
{
   // --- 工作 thread：数据库 I/O ---
   List<String> nomes = new ArrayList<>();
   try (SqliteDb db = SqliteDb.open("app.db");
        SqliteCursor c = db.query("SELECT name FROM person ORDER BY name"))
   {
      while (c.next())
         nomes.add(c.getString(0));   // 在离开 thread 之前采集
   }

   // --- 仅为更新组件而回到 UI ---
   Component.onUI(() ->
   {
      // 此处可安全操作组件（设置列表、repaint 等）
      preencheLista(nomes);
   });
}).start();
```

## 深入了解
- [后台工作](/docs/data-and-services/background-work) · [错误处理与日志](/docs/best-practices/seguranca)
- 姊妹配方：[参数化 CRUD](/docs/cookbook/data/crud) · [事务与批量写入](/docs/cookbook/data/transacao-e-bulk)
