APP下载

Electron | 数据存储:SQLite

原创

Electron

整理、归纳、分享 SQLite 的使用~

在 Electron 中,稍复杂的数据存储有两个选择:

  • IndexedDB,以 JSON 对象的方式存储数据,但是当数据较多时,复杂的条件查询效率不佳;
  • SQLite,它是一个关系型数据库,天生对复杂条件查询支持良好。
               
  • 1
SQLite 是使用 C 语言编写的嵌入式数据库引擎,它不像常见的客户端-服务器数据库范例,SQLite 内嵌到开发者的应用中,直接为开发者提供数据存储与访问的 API,数据存储介质可以是终端的内存也可以是磁盘,其特点是自给自足、无服务器、零配置、支持事务。它是在世界上部署最广泛的 SQL 数据库引擎。 COPY

使用

引入

  1. 在 node.js 中使用,常见的库:better-sqlite3 和 sqlite3,由于 better-sqlite3 在性能方面更出色,建议使用它作为 SQLite 包装器。

使用 knex.js

  1. 使用better-sqlite3读写数据库中的数据时,要书写 SQL 语句,这种语句是专门为数据库准备的指令,是不太符合现代编程语言的习惯的。

  2. knex.js 允许使用 JavaScript 代码来操作数据库里的数据和表结构,它会把 JavaScript 代码转义为具体的 SQL 语句,再把 SQL 语句交给数据库处理,可以把它理解为一种 SQL Builder。

  3. 使用下面代码初始化 SQLite 实例:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
import knex, { Knex } from 'knex'; import fs from 'fs'; import path from 'path'; let dbInstance: Knex; // @ts-ignore-next-line if (!dbInstance) { let dbPath = process.env.APP_DATA_PATH || (process.platform === 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + '/.local/share'); dbPath = path.join(dbPath, 'LittleBoy/db.db'); let dbIsExist = fs.existsSync(dbPath); if (!dbIsExist) { const resourceDBPath = path.join(process.execPath, '../resources/db.db'); fs.copyFileSync(resourceDBPath, dbPath); } dbInstance = knex({ client: 'better-sqlite3', connection: { filename: dbPath, }, useNullAsDefault: true, }); } export const db = dbInstance; COPY

上面代码导出一个数据库访问对象,只有第一次引入这个数据库访问对象的时候才会执行此对象的初始化逻辑,也就是说,无论我们在多少个组件中引入这个数据库访问对象,它只会被初始化一次,但这个约束只局限在一个进程内,也就是说对于整个应用而言,主进程有一个 db 实例,渲染进程也有一个 db 实例,两个实例是完全不同的。

注意: 由于渲染进程内的数据库访问对象和主进程内的数据库访问对象不是同一个对象,所以会有并发写入数据的问题,你需要控制好你的业务逻辑,避免两个进程在同一时间写入相同的业务数据。

第一次初始化数据库链接对象时,会检查 C:\Users\[username]\AppData\Roaming\[appname]\db.db 文件是否存在,如果不存在,就从应用程序安装目录 C:\Program Files\[appname]\resources\db.db 拷贝一份到该路径下,所以我们要提前把数据库设计好,基础数据也要初始化好,制作安装包的时候,把数据库文件打包到安装包里。

问题

  1. 如果出现下面的异常,表示需要使用 electron-build 重新编译:
               
  • 1
  • 2
  • 3
  • 4
  • 5
Error: The module '...node_modules\better-sqlite3\build\Release\better_sqlite3.node' was compiled against a different Node.js version using NODE_MODULE_VERSION $XYZ. This version of Node.js requires NODE_MODULE_VERSION $ABC. Please try re-compiling or re-installing the module (for instance, using `npm rebuild` or `npm install`). COPY

原因: Electron 内置的 Node.js 的版本可能与你编译原生模块使用的 Node.js 的版本不同导致的。

使用 electron-build 编译时,如何验证编译成功了呢?

当工程下出现了这个文件 node_modules\better-sqlite3\build\Release\better_sqlite3.node,才证明better_sqlite3模块编译成功了,如果上述指令没有完成这项工作,可以把指令配置到node_modules\better-sqlite3模块内部再执行一次,一般就可以编译成功了。

  1. 为什么要如此麻烦把数据库拷贝到C:\Users[username]\AppData\Roaming[appname]\目录下再访问,为什么不直接访问安装目录下的数据库文件呢?

因为当用户升级应用程序时安装目录下的文件都会被删除,因为我们可能会在数据库中放置很多用户数据,这样的话每次升级应用用户这些数据就都没了。

要点

  1. Electron 内置的 Node.js 中的一些模块也与 Node.js 发行版不同,比如 Electron 使用了 Chromium 的加密解密库 BoringSL,而 Node.js 发行版使用的是 OpenSSL 加密解密库。

解决方案:使用 Electron 团队提供的 electron-build 工具来处理,electron-rebuild 会帮我们确定 Electron 的版本号、Electron 内置的 Node.js 的版本号、以及 Node.js 使用的 ABI 的版本号,并根据这些版本号下载不同的头文件和类库。

在项目的 package.json 中加入下面的配置:

               
  • 1
  • 2
  • 3
  • 4
  • 5
{ "scripts": { "rebuild": "electron-rebuild -f -w better-sqlite3" } } COPY
  1. 原生模块不能被 vite 编译到 JavaScript 的。

  2. electron-builder 在制作安装包时,会自动为安装包安装相关依赖。但 electron-builder 会把很多无用的文件(很多编译原生模块时的中间产物)也附加到安装包内,无形中增加了安装包的体积。

  3. bindings模块是better-sqlite3模块依赖的一个模块,它的作用仅仅是确定原生模块文件better_sqlite3.node的路径。

  4. 可以在安装包的物料目录下,执行 asar list app.asar,查看打包到生产环境的文件,注意:需要全局安装 asar 工具。

  5. asar 是一种特殊的存档格式,它可以把大批的文件以一种无损、无压缩的方式链接在一起,并提供随机访问支持。默认情况下 electron-builder 会把开发者编写的 HTML、CSS 和 JavaScript 代码以及相关的资源打包成 asar 文件嵌入到安装包中,再分发给用户。electron-builder 是通过 Electron 官方提供的 asar 工具制成和提取 asar 文档的。开发者自己全局安装这个工具,随时查阅生产环境下的资源文件。(这是非常有必要的。)

  6. SQLite 不支持并发写入数据,两个或两个以上的写入操作同时执行时,只有一个写操作可以执行成功,其它的写操作会失败,并发读取数据没有问题。

  7. electron-builder 的一项配置:extraResources,可以让开发者为安装包指定额外的资源文件,electron-builder 打包应用时会把这些资源文件附加到安装包内,当用户安装应用程序时,这些资源会释放在安装目录的 resources\子目录下。

  8. 同步操作会阻塞 JavaScript 的执行线程,所以应用中一定要谨慎使用同步操作。 常见的如:Node.js 提供的类似 fs.copyFileSync,Electron 提供的 dialog.showOpenDialogSync 这样的方法等,但好在同步方法一般都有对应的异步方法来替代。

参考资料

  1. SQLite - https://sqlite.org/index.html .
  2. node-sqlite3 - https://github.com/TryGhost/node-sqlite3 .
  3. better-sqlite3 - https://github.com/WiseLibs/better-sqlite3 .
  4. electron-build - https://github.com/electron/electron-rebuild .
  5. electron/asar - https://github.com/electron/asar .

大家加油 :)

评论区

写评论

登录

所以,就随便说点什么吧...

这里什么都没有,快来评论吧...