本文首发同名微信公众号:前端徐徐
大家好,我是徐徐。今天我们讲讲如何在 Electron 中集成本地数据库,这一节主要讲如何集成 SQLite。
前言
上一节我们比较了三种数据库,我个人认为 SQLite3 这个数据库工具适用的范围最广,也相对稳定,所以我们这一节就专门讲如何在 Electron 中集成 SQLite3,如果大家有其他想要的案例,可以在评论区留言。话不多说,我们进入正题。
准备工作
- 安装 sqlitestudio:https://sqlitestudio.pl
sqlitestudio 是一款非常方便的 SQLite 数据库管理工具,能够帮助我们在开发过程中查看和编辑本地数据库中的数据。安装完成后,你可以用它打开本地的 SQLite 数据库文件,查看数据库表和数据结构,进行调试和管理。
- 安装 sqlite3 依赖包:
yarn add sqlite3
注意:在这里安装的时候我们需要使其安装在 dependencies
下,不然后期打包会有问题,因为涉及到底层的原生模块,所以我们为了方便就把整个包直接放在包里面。
封装增删改查 API
- src/common/db/api.ts
import { app } from "electron";
import * as path from "path";
import * as sqlite3 from "sqlite3";
const userDataPath = app.getPath("userData");
const dbPath = path.join(userDataPath, "sqliteDatabase.db");
export interface queryParam {
sql: string;
params?: any[];
}
export interface insertParam {
table: string;
data: { [key: string]: any };
}
export interface updateParam {
table: string;
data: { [key: string]: any };
condition: string;
}
export interface deleteParam {
table: string;
condition: string;
}
class Database {
private static instance: Database;
private db: sqlite3.Database;
private constructor() {
this.db = new sqlite3.Database(dbPath);
}
static async getInstance(): Promise<Database> {
if (!Database.instance) {
Database.instance = new Database();
await Database.instance.open();
await Database.instance.initializeSchema();
}
return Database.instance;
}
private open(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.db.serialize(() => {
this.db.run("PRAGMA foreign_keys = ON", (err) => {
if (err) {
reject(err);
} else {
console.log("Connected to the database.");
resolve();
}
});
});
});
}
private initializeSchema(): Promise<void> {
return this.query({
sql: `
CREATE TABLE IF NOT EXISTS test (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER
)
`,
}).then(() => {
console.log("Database schema initialized.");
}).catch((err) => {
console.error("Error initializing database schema:", err);
});
}
close(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.db.close((err) => {
if (err) {
reject(err);
} else {
console.log("Database closed.");
resolve();
}
});
});
}
query(param: queryParam): Promise<any[]> {
return new Promise<any[]>((resolve, reject) => {
this.db.all(param.sql, param.params, (err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows);
}
});
});
}
insert(param: insertParam): Promise<number> {
return new Promise<number>((resolve, reject) => {
const keys = Object.keys(param.data);
const values = Object.values(param.data);
const placeholders = keys.map(() => "?").join(",");
const sql = `INSERT INTO ${param.table} (${keys.join(
","
)}) VALUES (${placeholders})`;
this.db.run(sql, values, function (err) {
if (err) {
reject(err);
} else {
resolve(this.lastID);
}
});
});
}
update(param: updateParam): Promise<number> {
return new Promise<number>((resolve, reject) => {
const entries = Object.entries(param.data)
.map(([key, value]) => `${key} = ?`)
.join(",");
const params = Object.values(param.data);
const sql = `UPDATE ${param.table} SET ${entries} WHERE ${param.condition}`;
this.db.run(sql, params, function (err) {
if (err) {
reject(err);
} else {
resolve(this.changes);
}
});
});
}
delete(param: deleteParam): Promise<void> {
return new Promise<void>((resolve, reject) => {
const sql = `DELETE FROM ${param.table} WHERE ${param.condition}`;
this.db.run(sql, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
}
// Wrap database calls to ensure initialization
const getDatabase = async (): Promise<Database> => {
return await Database.getInstance();
};
export const sqQuery = async (param: queryParam) => {
const db = await getDatabase();
return db.query(param);
};
export const sqInsert = async (param: insertParam) => {
const db = await getDatabase();
return db.insert(param);
};
export const sqUpdate = async (param: updateParam) => {
const db = await getDatabase();
return db.update(param);
};
export const sqDelete = async (param: deleteParam) => {
const db = await getDatabase();
return db.delete(param);
};
上面这个文件定义了一个使用单例模式的 SQLite 数据库管理类 Database
,确保在首次访问数据库时初始化数据库,并包含表的创建和升级逻辑,后期如果想升级或者改表的话只需要在 initializeSchema
中提供新的建表语句即可。提供 query
、insert
、update
和 delete
方法,用于执行数据库操作,同时确保在执行任何操作之前数据库已经初始化。所有数据库操作都通过 getDatabase
方法获取单例实例,保证了数据库连接的唯一性和初始化的完成。
完成了底层 API 的构建,我们需要提供给外部一些通用的方法,然后可以在主进程和渲染进程都能使用。
- src/preload/index.ts
前置脚本注册供渲染进程使用
sqQuery: (param: queryParam) => {
return ipcRenderer.invoke('sqQuery', param)
},
sqInsert: (param: insertParam) => {
return ipcRenderer.invoke('sqInsert', param)
},
sqUpdate: (param: updateParam) => {
return ipcRenderer.invoke('sqUpdate', param)
},
sqDelete: (param: deleteParam) => {
return ipcRenderer.invoke('sqDelete', param)
}
- src/main/ipc/index.ts
主进程 IPC 处理
ipcMain.handle('sqQuery', (event: IpcMainInvokeEvent,param: queryParam): Promise<any> => {
return sqQuery(param);
});
ipcMain.handle('sqInsert', (event: IpcMainInvokeEvent,param: insertParam): Promise<any> => {
return sqInsert(param);
});
ipcMain.handle('sqUpdate', (event: IpcMainInvokeEvent,param: updateParam): Promise<any> => {
return sqUpdate(param);
});
ipcMain.handle('sqDelete', (event: IpcMainInvokeEvent,param: deleteParam): Promise<any> => {
return sqDelete(param);
});
- src/common/db/index.ts
融合 API 供外部方便调用
import type { queryParam, insertParam, updateParam, deleteParam } from "./api";
export const sqQuery = (param: queryParam) => {
if (import.meta.env.VITE_CURRENT_RUN_MODE === "render") {
return window.electronAPI.sqQuery(param);
} else {
return import("./api").then((module) => module.sqQuery(param));
}
};
export const sqInsert = (param: insertParam) => {
if (import.meta.env.VITE_CURRENT_RUN_MODE === "render") {
return window.electronAPI.sqInsert(param);
} else {
return import("./api").then((module) => module.sqInsert(param));
}
};
export const sqUpdate = (param: updateParam) => {
if (import.meta.env.VITE_CURRENT_RUN_MODE === "render") {
return window.electronAPI.sqUpdate(param);
} else {
return import("./api").then((module) => module.sqUpdate(param));
}
};
export const sqDelete = (param: deleteParam) => {
if (import.meta.env.VITE_CURRENT_RUN_MODE === "render") {
return window.electronAPI.sqDelete(param);
} else {
return import("./api").then((module) => module.sqDelete(param));
}
};
export { queryParam, insertParam, updateParam, deleteParam };
整体总结一下:
- 前置脚本 (Preload):提供了一个与渲染进程交互的接口,使用
ipcRenderer
调用主进程的功能。 - 主进程 IPC 处理:使用
ipcMain.handle
监听渲染进程的请求,并执行相应的数据库操作。 - 公共 API 封装:通过判断当前运行模式,提供跨平台的数据库操作封装,确保在渲染进程和主进程中都能调用相应的 API。
经过上面的动作,你就可以在 Electron 中使用数据库了。
结语
在本节中,我们深入探讨了如何在 Electron 项目中集成 SQLite3 数据库。通过安装相关工具和依赖包,并配置好环境,我们为后续的数据库操作奠定了基础。我们可以在此基础上实现更多功能,例如事务处理、性能优化等,进一步增强应用的功能。
随着技术的不断发展,桌面应用对数据存储的需求也在不断增加。掌握如何集成 SQLite3 这样的轻量级数据库,将帮助我们在构建更复杂的 Electron 应用时,更好地管理和利用本地数据存储。