本地存储与数据库
本教程共 48 篇 · 第 38 篇 · 更新于 2026-08-09 · 约 9 分钟阅读
本节目标:读完你能用
@tauri-apps/plugin-store存取键值对,用@tauri-apps/plugin-sql连接 SQLite 数据库并执行 SQL,还能根据场景选对存储方案。
桌面应用经常需要在本地存东西:用户的主题偏好、待办列表、缓存数据。Tauri 2 提供了两个官方插件来搞定这件事——plugin-store 适合轻量的键值存储,plugin-sql 适合需要复杂查询的关系型数据库。一个像便利贴,一个像档案柜,各有各的用武之地。
plugin-store:键值存储
plugin-store 的思路很简单:把数据以 JSON 格式存到文件里,前端用 set / get 读写,像用 localStorage 一样简单。但它比 localStorage 强在——数据持久化到磁盘,应用重启后还在。
安装与注册
两端都要装:
cd src-tauri
cargo add tauri-plugin-store
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::new().build())
.run(tauri::generate_context!())
.expect("运行 Tauri 应用时出错");
}
npm install @tauri-apps/plugin-store
读写键值对
前端的用法非常直白:
import { load } from "@tauri-apps/plugin-store";
// 加载(或创建)一个 store 文件
const store = await load("settings.json", { autoSave: false });
// 写入一个键值对
await store.set("theme", "dark");
await store.set("volume", 80);
await store.set("user", { name: "小明", age: 20 });
// 读取
const theme = await store.get("theme");
console.log(theme); // "dark"
const user = await store.get("user");
console.log(user); // { name: "小明", age: 20 }
// 手动保存到磁盘
await store.save();
load("settings.json") 会创建或读取一个名为 settings.json 的文件,位置在应用的数据目录下。set 只是改了内存里的值,要真正写入磁盘需要调 save()。
Tip
autoSave不设false时,默认行为是防抖保存——改完数据后等 100ms 没有新改动就自动写盘。如果你频繁修改数据又不想手动调save(),直接用默认的autoSave就行:const store = await load("settings.json")。
LazyStore:懒加载
如果你不想在应用启动时就加载 store 文件,可以用 LazyStore——它只在第一次访问时才真正加载:
import { LazyStore } from "@tauri-apps/plugin-store";
const store = new LazyStore("settings.json");
// 第一次调用 get/set 时才真正读取文件
await store.set("theme", "light");
const theme = await store.get("theme");
LazyStore 和 load() 返回的 Store 接口一样,区别只是加载时机。对于大文件或不一定用到的 store,用 LazyStore 能省启动时间。
Rust 端操作 store
store 也能在 Rust 端直接读写。用 app.store() 拿到同一个 store 实例(和前端共享):
use tauri_plugin_store::StoreExt;
use serde_json::json;
// 在 setup 或命令里
let store = app.store("settings.json")?;
store.set("theme", json!("dark"));
store.set("user", json!({ "name": "小明", "age": 20 }));
let value = store.get("theme").expect("没取到");
println!("{}", value); // "dark"
NoteRust 端存入的值必须是
serde_json::Value类型,这样才能和前端 JS 兼容。用json!宏可以方便地构造 JSON 值。
store 的权限
和所有插件一样,store 默认是锁住的。在 capabilities/default.json 中加 store:default:
{
"permissions": [
"core:default",
"store:default"
]
}
plugin-sql:关系型数据库
当数据量大了、需要复杂查询时,键值存储就不够用了。plugin-sql 让前端直接连 SQLite、MySQL 或 PostgreSQL,底层用的是 Rust 的 sqlx 库。
安装与选择数据库引擎
先装插件,再选一个数据库引擎:
cd src-tauri
cargo add tauri-plugin-sql
cargo add tauri-plugin-sql --features sqlite
--features 决定启用哪个驱动。SQLite 最常用(本地应用首选),MySQL 和 PostgreSQL 适合需要连远程数据库的场景。三个可以同时启用。
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_sql::Builder::default().build())
.run(tauri::generate_context!())
.expect("运行 Tauri 应用时出错");
}
npm install @tauri-apps/plugin-sql
连接数据库并执行 SQL
前端连接 SQLite 非常简单:
import Database from "@tauri-apps/plugin-sql";
// 连接(不存在则创建)一个 SQLite 数据库文件
const db = await Database.load("sqlite:test.db");
// 建表
await db.execute(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
status TEXT DEFAULT 'pending'
)
`);
// 插入数据(用 $1, $2 占位)
await db.execute(
"INSERT INTO todos (title, status) VALUES ($1, $2)",
["学 Tauri", "done"]
);
// 查询
const rows = await db.select("SELECT * FROM todos");
console.log(rows);
// [{ id: 1, title: "学 Tauri", status: "done" }]
Database.load("sqlite:test.db") 中的 sqlite: 前缀表示用 SQLite 引擎,test.db 是文件名,路径相对于应用配置目录(BaseDirectory::AppConfig)。MySQL 和 PostgreSQL 则传完整的连接字符串:
// MySQL
const db = await Database.load("mysql://user:password@host/test");
// PostgreSQL
const db = await Database.load("postgres://user:password@host/test");
Tip不同数据库的占位符语法不同:SQLite 和 PostgreSQL 使用
$1, $2, $3(编号式),MySQL 使用?, ?, ?(问号式)。参数以数组传入,按顺序对应。
查询与参数绑定
execute 用于写操作(INSERT / UPDATE / DELETE),返回受影响的行数。select 用于读操作(SELECT),返回对象数组:
// 更新
const result = await db.execute(
"UPDATE todos SET status = $1 WHERE id = $2",
["done", 1]
);
console.log(result.rowsAffected); // 1
// 查询
const rows = await db.select(
"SELECT * FROM todos WHERE status = $1",
["done"]
);
console.log(rows);
// [{ id: 1, title: "学 Tauri", status: "done" }]
数据库迁移(Migrations)
实际应用中,数据库表结构会随版本迭代而变化。plugin-sql 内置了迁移(migration)机制,让你用版本号管理表结构变更:
use tauri_plugin_sql::{Builder, Migration, MigrationKind};
let migrations = vec![
Migration {
version: 1,
description: "create_initial_tables",
sql: "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);",
kind: MigrationKind::Up,
},
Migration {
version: 2,
description: "add_email_column",
sql: "ALTER TABLE users ADD COLUMN email TEXT;",
kind: MigrationKind::Up,
},
];
tauri::Builder::default()
.plugin(
Builder::default()
.add_migrations("sqlite:mydatabase.db", migrations)
.build(),
)
.run(tauri::generate_context!())
.expect("运行 Tauri 应用时出错");
每个 Migration 有四个字段:版本号、描述、SQL 语句、迁移方向(Up 表示正向执行,Down 表示回滚)。插件会自动记录已执行的迁移版本,下次启动时只跑还没执行过的。
Note所有迁移都在事务中执行,保证原子性。如果某条迁移失败了,整个事务会回滚,数据库保持一致状态——不会出现「跑到一半表建了一半」的尴尬。
你也可以让迁移在前端 load() 时自动触发:
const db = await Database.load("sqlite:mydatabase.db");
// load 时会检查并执行未完成的迁移
如果需要在应用启动时就准备好数据库,可以在 Rust 端通过 .plugin(tauri_plugin_sql::Builder::default().add_migrations(...).build()) 注册迁移,插件会在启动时自动执行;也可以直接在前端首次调用 Database.load() 时加载。
sql 的权限
plugin-sql 默认也是锁住的。前端要用,得在 capabilities/default.json 里授权:
{
"permissions": [
"core:default",
"sql:default",
"sql:allow-execute"
]
}
sql:default 开放基础能力,sql:allow-execute 允许执行 SQL 语句。如果你只想让前端查询不允许修改,可以只开 sql:allow-select 而不给 sql:allow-execute,实现读写分离控制。
两种方案怎么选
| 维度 | plugin-store | plugin-sql |
|---|---|---|
| 数据模型 | 键值对(JSON) | 关系型(表、行、列) |
| 查询能力 | 只能按 key 取 | 支持 SQL 全套查询 |
| 适合场景 | 配置、偏好、小量缓存 | 待办、笔记、业务数据 |
| 复杂度 | 极低,开箱即用 | 需要 SQL 基础 |
| 数据量 | 几百到几千个 key | 可处理百万级行 |
一句话总结:数据简单、不需要查询,用 store;数据有结构、需要筛选和关联,用 sql。两者并不互斥,同一个应用里可以同时用——配置存 store,业务数据存 SQLite。
小结
Tauri 2 提供两个本地存储插件:plugin-store 是轻量 KV 存储,用 load() 打开、set() / get() 读写、save() 持久化,适合存配置和偏好;plugin-sql 是关系型数据库接口,支持 SQLite / MySQL / PostgreSQL,用 Database.load() 连接、execute() 执行写操作、select() 执行查询,内置版本化迁移机制。两个插件都遵循 Tauri 2 的权限模型——默认全锁,需要在 capabilities 里显式授权。选择方案时看数据模型:简单键值用 store,有结构有关系就用 sql。