Paper API · Paper 資料
SQLite 持久化 Repository
Paper 插件的 SQLite 持久化:單一連線(WAL、busy_timeout、foreign_keys)、user_version 遷移、無 Bukkit 的 Repository,以及 onDisable 同步落盤的單一寫入執行緒 write-behind。
paper-sqlite-repository更新於 2026年10月9日觸發關鍵字
SQLite資料庫持久化schema 遷移user_versionwrite-behindsqlite-jdbcSQLITE_BUSYPaper SQLite Repository
目的
給 Paper 插件一套小而可靠的 SQLite 持久化骨架:一條連線加 WAL、依 PRAGMA user_version 的有序遷移、不含 Bukkit 的 Repository,以及單一寫入執行緒的 write-behind。領域層只用 JDK 型別,所以可用 jdbc:sqlite::memory: 單元測試,不需要 MockBukkit。
平台需求
- Paper 1.21.11 / 26.2(兩版皆經編譯驗證)
- 純 Paper API,不需要 Paperweight
org.xerial:sqlite-jdbc含 JNI,不可 shade:compileOnly+plugin.yml的libraries:
產生的代碼
SqliteDatabase.java(pragma)
Connection opened = DriverManager.getConnection(jdbcUrl);
try (Statement stmt = opened.createStatement()) {
stmt.execute("PRAGMA journal_mode=WAL");
stmt.execute("PRAGMA busy_timeout=3000");
stmt.execute("PRAGMA foreign_keys=ON");
}
HomeSchema.java(遷移只往後加)
public static final List<SchemaMigrations.Step> STEPS = List.of(
SchemaMigrations.Step.of("CREATE TABLE homes (...)"), // v1,已發布不可改
SchemaMigrations.Step.of("ALTER TABLE homes ADD COLUMN created_at INTEGER NOT NULL DEFAULT 0") // v2
);
SqliteHomeRepository.java(PreparedStatement)
try (PreparedStatement ps = database.connection().prepareStatement(
"SELECT owner, name, world, x, y, z, yaw, pitch FROM homes WHERE owner = ?")) {
ps.setString(1, owner.toString());
try (ResultSet rs = ps.executeQuery()) { ... }
}
HomesPlugin.java(生命週期)
getServer().getScheduler().runTaskTimerAsynchronously(this, flusher::requestFlush, 100L, 100L);
@Override
public void onDisable() {
flusher.close(10L); // 排程器已關閉:同步排空
database.close(); // 最後才關資料庫
}
規則
- 遷移步驟只能往後加,已發布的步驟不得重排或修改;每步一個交易
- SQL 一律
PreparedStatement+ try-with-resources,值用?綁定 - 領域型別與 Repository 介面不含 Bukkit 型別
- sqlite-jdbc 不 shade/relocate;
libraries:只有plugin.yml支援
執行緒安全
- 主執行緒不碰 JDBC:讀取走
runTaskAsynchronously,結果以runTask排回主執行緒 - 所有寫入經單一寫入執行緒;共用的單一連線由 Repository 的
synchronized保護 - dirty map 儲存最新意圖,排空用
remove(key, value)避免蓋掉新值 onDisable不可再排任務:先停 executor、等待、同步排空,最後關資料庫