Electron 本地数据库实践指南(下)— 集成SQLite

 本文首发同名微信公众号:前端徐徐 

大家好,我是徐徐。今天我们讲讲如何在 Electron 中集成本地数据库,这一节主要讲如何集成 SQLite。

前言

上一节我们比较了三种数据库,我个人认为 SQLite3 这个数据库工具适用的范围最广,也相对稳定,所以我们这一节就专门讲如何在 Electron 中集成 SQLite3,如果大家有其他想要的案例,可以在评论区留言。话不多说,我们进入正题。

准备工作

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 中提供新的建表语句即可。提供 queryinsertupdatedelete 方法,用于执行数据库操作,同时确保在执行任何操作之前数据库已经初始化。所有数据库操作都通过 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 应用时,更好地管理和利用本地数据存储。

源码

GitHub - Xutaotaotao/electron-proplay at feature-db

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值