数据库集成 #

一、数据库方案概述 #

1.1 数据库选择 #

数据库 特点 使用场景
SQLite 轻量级、嵌入式 本地数据存储
tauri-plugin-sql 官方插件 简化数据库操作
sled 纯 Rust KV 存储 高性能键值存储
redb 纯 Rust 数据库 简单数据存储

1.2 架构设计 #

text
┌─────────────────────────────────────────────────────────────┐
│                      数据库架构                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    前端 API                          │   │
│  │  @tauri-apps/plugin-sql                             │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                                │
│                            │ IPC                            │
│                            ▼                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    Rust 后端                         │   │
│  │  tauri-plugin-sql / rusqlite                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                                │
│                            ▼                                │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    数据库文件                        │   │
│  │  SQLite / sled / redb                               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、tauri-plugin-sql #

2.1 安装插件 #

bash
pnpm add @tauri-apps/plugin-sql
toml
# Cargo.toml
[dependencies]
tauri-plugin-sql = { version = "2", features = ["sqlite"] }

2.2 注册插件 #

rust
// src-tauri/src/lib.rs
tauri::Builder::default()
    .plugin(tauri_plugin_sql::Builder::default().build())
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

2.3 权限配置 #

json
// src-tauri/capabilities/default.json
{
    "permissions": [
        "sql:default",
        "sql:allow-load",
        "sql:allow-execute",
        "sql:allow-select"
    ]
}

三、SQLite 基础 #

3.1 创建数据库连接 #

typescript
import Database from '@tauri-apps/plugin-sql';

const db = await Database.load('sqlite:app.db');

3.2 创建表 #

typescript
await db.execute(`
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
`);

3.3 插入数据 #

typescript
// 插入单条
await db.execute(
    'INSERT INTO users (name, email) VALUES ($1, $2)',
    ['Alice', 'alice@example.com']
);

// 获取插入的 ID
const result = await db.execute(
    'INSERT INTO users (name, email) VALUES ($1, $2)',
    ['Bob', 'bob@example.com']
);
console.log('Last insert ID:', result.lastInsertId);

3.4 查询数据 #

typescript
// 查询多条
const users = await db.select<User[]>(
    'SELECT * FROM users ORDER BY created_at DESC'
);

// 查询单条
const user = await db.select<User[]>(
    'SELECT * FROM users WHERE id = $1',
    [1]
);

// 条件查询
const activeUsers = await db.select<User[]>(
    'SELECT * FROM users WHERE email LIKE $1',
    ['%@example.com']
);

3.5 更新数据 #

typescript
await db.execute(
    'UPDATE users SET name = $1 WHERE id = $2',
    ['Alice Updated', 1]
);

3.6 删除数据 #

typescript
await db.execute(
    'DELETE FROM users WHERE id = $1',
    [1]
);

四、数据库封装 #

4.1 数据库管理类 #

typescript
// db/database.ts
import Database from '@tauri-apps/plugin-sql';

export class DatabaseManager {
    private db: Database | null = null;
    private dbName: string;

    constructor(dbName: string = 'app.db') {
        this.dbName = dbName;
    }

    async init(): Promise<void> {
        this.db = await Database.load(`sqlite:${this.dbName}`);
        await this.createTables();
    }

    private async createTables(): Promise<void> {
        await this.db!.execute(`
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                email TEXT UNIQUE NOT NULL,
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP
            );

            CREATE TABLE IF NOT EXISTS posts (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                content TEXT,
                author_id INTEGER NOT NULL,
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (author_id) REFERENCES users(id)
            );
        `);
    }

    async query<T>(sql: string, params: unknown[] = []): Promise<T[]> {
        return this.db!.select<T[]>(sql, params);
    }

    async execute(sql: string, params: unknown[] = []): Promise<void> {
        await this.db!.execute(sql, params);
    }

    async transaction<T>(callback: () => Promise<T>): Promise<T> {
        await this.db!.execute('BEGIN TRANSACTION');
        try {
            const result = await callback();
            await this.db!.execute('COMMIT');
            return result;
        } catch (error) {
            await this.db!.execute('ROLLBACK');
            throw error;
        }
    }

    async close(): Promise<void> {
        if (this.db) {
            await this.db.close();
            this.db = null;
        }
    }
}

export const db = new DatabaseManager();

4.2 Repository 模式 #

typescript
// db/repositories/userRepository.ts
import { db } from '../database';

export interface User {
    id: number;
    name: string;
    email: string;
    createdAt: string;
}

export class UserRepository {
    static async findAll(): Promise<User[]> {
        return db.query<User>('SELECT * FROM users ORDER BY created_at DESC');
    }

    static async findById(id: number): Promise<User | null> {
        const users = await db.query<User>(
            'SELECT * FROM users WHERE id = $1',
            [id]
        );
        return users[0] || null;
    }

    static async findByEmail(email: string): Promise<User | null> {
        const users = await db.query<User>(
            'SELECT * FROM users WHERE email = $1',
            [email]
        );
        return users[0] || null;
    }

    static async create(name: string, email: string): Promise<number> {
        await db.execute(
            'INSERT INTO users (name, email) VALUES ($1, $2)',
            [name, email]
        );
        const result = await db.query<{ id: number }>(
            'SELECT last_insert_rowid() as id'
        );
        return result[0].id;
    }

    static async update(id: number, data: Partial<User>): Promise<void> {
        const fields: string[] = [];
        const values: unknown[] = [];

        if (data.name) {
            fields.push('name = $' + (fields.length + 1));
            values.push(data.name);
        }
        if (data.email) {
            fields.push('email = $' + (fields.length + 1));
            values.push(data.email);
        }

        if (fields.length > 0) {
            values.push(id);
            await db.execute(
                `UPDATE users SET ${fields.join(', ')} WHERE id = $${fields.length + 1}`,
                values
            );
        }
    }

    static async delete(id: number): Promise<void> {
        await db.execute('DELETE FROM users WHERE id = $1', [id]);
    }
}

五、事务处理 #

5.1 基本事务 #

typescript
await db.transaction(async () => {
    // 创建用户
    await db.execute(
        'INSERT INTO users (name, email) VALUES ($1, $2)',
        ['Alice', 'alice@example.com']
    );

    // 创建关联文章
    await db.execute(
        'INSERT INTO posts (title, author_id) VALUES ($1, $2)',
        ['First Post', 1]
    );
});

5.2 批量操作 #

typescript
async function batchInsert(users: { name: string; email: string }[]) {
    await db.transaction(async () => {
        for (const user of users) {
            await db.execute(
                'INSERT INTO users (name, email) VALUES ($1, $2)',
                [user.name, user.email]
            );
        }
    });
}

六、数据迁移 #

6.1 迁移系统 #

typescript
// db/migrations.ts
import { db } from './database';

interface Migration {
    version: number;
    name: string;
    up: string;
    down: string;
}

const migrations: Migration[] = [
    {
        version: 1,
        name: 'create_users_table',
        up: `
            CREATE TABLE users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                email TEXT UNIQUE NOT NULL,
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP
            )
        `,
        down: 'DROP TABLE users',
    },
    {
        version: 2,
        name: 'add_user_avatar',
        up: 'ALTER TABLE users ADD COLUMN avatar TEXT',
        down: 'ALTER TABLE users DROP COLUMN avatar',
    },
];

export async function runMigrations(): Promise<void> {
    await db.execute(`
        CREATE TABLE IF NOT EXISTS migrations (
            version INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
    `);

    const applied = await db.query<{ version: number }>(
        'SELECT version FROM migrations ORDER BY version'
    );

    const appliedVersions = new Set(applied.map((m) => m.version));

    for (const migration of migrations) {
        if (!appliedVersions.has(migration.version)) {
            await db.transaction(async () => {
                await db.execute(migration.up);
                await db.execute(
                    'INSERT INTO migrations (version, name) VALUES ($1, $2)',
                    [migration.version, migration.name]
                );
            });
            console.log(`Applied migration: ${migration.name}`);
        }
    }
}

七、Rust 后端数据库 #

7.1 使用 rusqlite #

toml
# Cargo.toml
[dependencies]
rusqlite = { version = "0.31", features = ["bundled"] }
rust
use rusqlite::{Connection, Result};

#[tauri::command]
fn get_users() -> Result<Vec<User>, String> {
    let conn = Connection::open("app.db").map_err(|e| e.to_string())?;
    
    let mut stmt = conn
        .prepare("SELECT id, name, email FROM users")
        .map_err(|e| e.to_string())?;
    
    let users = stmt
        .query_map([], |row| {
            Ok(User {
                id: row.get(0)?,
                name: row.get(1)?,
                email: row.get(2)?,
            })
        })
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;
    
    Ok(users)
}

#[derive(serde::Serialize)]
struct User {
    id: i32,
    name: String,
    email: String,
}

7.2 使用状态管理连接 #

rust
use std::sync::Mutex;
use rusqlite::Connection;

struct DbState {
    conn: Mutex<Connection>,
}

#[tauri::command]
fn query_users(state: tauri::State<DbState>) -> Result<Vec<User>, String> {
    let conn = state.conn.lock().unwrap();
    
    let mut stmt = conn
        .prepare("SELECT id, name, email FROM users")
        .map_err(|e| e.to_string())?;
    
    // ...
}

fn main() {
    let conn = Connection::open("app.db").expect("Failed to open database");
    
    tauri::Builder::default()
        .manage(DbState {
            conn: Mutex::new(conn),
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

八、最佳实践 #

8.1 索引优化 #

sql
-- 创建索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_author ON posts(author_id);

-- 复合索引
CREATE INDEX idx_posts_author_date ON posts(author_id, created_at);

8.2 查询优化 #

typescript
// 使用 LIMIT 限制结果
const recentPosts = await db.query<Post>(
    'SELECT * FROM posts ORDER BY created_at DESC LIMIT 10'
);

// 只选择需要的字段
const userEmails = await db.query<{ email: string }>(
    'SELECT email FROM users'
);

8.3 数据备份 #

typescript
import { copyFile } from '@tauri-apps/plugin-fs';
import { appDataDir, join } from '@tauri-apps/api/path';

async function backupDatabase(): Promise<string> {
    const dbPath = await join(await appDataDir(), 'app.db');
    const backupPath = await join(
        await appDataDir(),
        `backups/app_${Date.now()}.db`
    );
    
    await copyFile(dbPath, backupPath);
    return backupPath;
}

九、总结 #

9.1 核心要点 #

要点 说明
插件使用 tauri-plugin-sql
数据库选择 SQLite 适合本地存储
Repository 封装数据访问逻辑
事务处理 确保数据一致性
数据迁移 版本化管理数据库

9.2 下一步 #

现在你已经掌握了数据库集成,接下来让我们学习 数据持久化,了解数据持久化的最佳实践!

最后更新:2026-03-28