sql-petra-orm
v1.0.7
Published
SQLite数据库ORM框架
Readme
SQLPetra
SQLPetra 是一个基于 TypeScript 装饰器和 SQLite 的轻量级本地 ORM。底层使用 better-sqlite3,对外通过 Db 提供数据库初始化、自动建表、查询、新增、修改、删除、事务、备份和原生 SQL 执行能力。
适用范围
适合:
- Electron 主进程中的本地 SQLite 数据访问。
- 小型桌面工具、离线应用、本地配置库、缓存库。
- 不想引入完整 ORM,但希望少写重复 CRUD SQL 的项目。
- 希望源码简单、可控、方便按业务修改的项目。
- 如果集成与
electron程序的话在主进程是可以直接使用的,但是渲染进程要使用的话则需要查看扩展方法中的集成Electron程序中有说明
暂不适合:
- 强依赖复杂关系映射的系统。
- 需要完整迁移系统、跨数据库兼容、连接池的大型服务端项目。
- 需要复杂 Join、级联、实体生命周期钩子的完整 ORM 场景。
注意:
electron渲染进程目前不可使用该依赖,不然运行就会报错,后期会处理针对于渲染进程使用的方法
Db 方法总览
| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 |
| --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------- |
| Db.InitDb(config) | 打开或创建 SQLite 数据库文件,并设置 WAL 模式 | 程序启动时先调用,用来建立后续所有 ORM 操作需要的数据库连接 | void |
| Db.CreateTableByEntity(Entity) | 根据实体字段执行 CREATE TABLE;表已存在时执行 ALTER TABLE ADD COLUMN 追加新列 | 用实体类同步数据库表结构,适合应用启动时自动建表或补字段 | void |
| Db.Close() | 关闭当前 SQLite 连接 | 程序退出、释放数据库文件占用时调用 | void |
| Db.Backup(path) | 调用 SQLite 备份能力,把当前数据库复制到指定文件 | 用来做本地数据备份、导出数据库文件 | void |
| Db.Transaction(callback) | 开启数据库事务;成功提交,异常回滚 | 把多次新增、修改、删除放到一个整体里,保证要么全部成功,要么全部撤销 | TransActionReturnType |
| Db.Queryable(Entity) | 准备生成 SELECT ... FROM 表 查询语句 | 查询数据入口,后面继续接 Where、Select、ToList、First 等方法 | Queryable<T> |
| Db.Insertable(Entity, data) | 准备生成 INSERT INTO 表(...) VALUES(...) 插入语句 | 往表中新增一行或多行数据 | Insertable<T> |
| Db.Updateable(Entity) | 准备生成 UPDATE 表 SET ... WHERE ... 更新语句 | 修改表中已有数据,必须配合 SetColumnFiled / SetColumns 和条件使用 | Updateable<T> |
| Db.Deleteable(Entity) | 准备生成 DELETE FROM 表 WHERE ... 删除语句 | 删除表中已有数据,默认必须加条件,避免误删全表 | Deleteable<T> |
| Db.RunSQLQueryable(sql, args?) | 直接执行自定义 SELECT SQL | 链式查询不够用时,手写 SQL 查询数据 | any |
| Db.RunSQLInsertable(sql, args?) | 直接执行自定义 INSERT SQL | 链式新增不够用时,手写 SQL 插入数据 | number |
| Db.RunSQLUpdateable(sql, args?) | 直接执行自定义 UPDATE SQL | 链式更新不够用时,手写 SQL 修改数据 | number |
| Db.RunSQLDeleteable(sql, args?) | 直接执行自定义 DELETE SQL | 链式删除不够用时,手写 SQL 删除数据 | number |
快速开始
安装
// 使用npm
npm i sql-petra-orm
// 或者使用pnpm
pnpm add sql-petra-orm注意安装后如果是electron程序的话是启动不起来的,因为项目依赖better-sqlite3 并且``better-sqlite3是原生 Node.js模块。如果出现better_sqlite3.node 找不到、NODE_MODULE_VERSION 不匹配、The module was compiled against a different Node.js version等错误,通常执行pnpm rebuild better-sqlite3或npm rebuild better-sqlite3` 即可。
Electron 项目需要按当前 Electron 版本重新编译原生模块:
npm i -D @electron/rebuild
npx electron-rebuild -f -w better-sqlite3卸载
npm uninstall sql-petra-orm更新
npm update sql-petra-orm初始化数据库
要使用数据库前提要初始化
Db.InitDb({
dbPath: "F:\\项目\\SQLPetra\\",
dbName: "test.db",
isWriteLog: true
});参数说明:
| 参数 | 类型 | 必填 | 说明 |
| ------------ | --------- | ---- | ---------------------------------------- |
| dbPath | string | 是 | 数据库文件所在目录。目录不存在会自动创建 |
| dbName | string | 是 | 数据库文件名。不带 .db 时会自动补上 |
| isWriteLog | boolean | 是 | 是否打印 SQL、参数和事务日志 |
初始化后会打开 SQLite 数据库,并执行:
PRAGMA journal_mode = WAL;解决报错
better-sqlite3 是原生 Node.js 模块。如果出现 better_sqlite3.node 找不到、NODE_MODULE_VERSION 不匹配、The module was compiled against a different Node.js version 等错误,通常执行 pnpm rebuild better-sqlite3 或 npm rebuild better-sqlite3 即可。
Electron 项目需要按当前 Electron 版本重新编译原生模块:
npm i -D @electron/rebuild
npx electron-rebuild -f -w better-sqlite3建议在 Electron 项目的 package.json 里加:
{
"scripts": {
"postinstall": "electron-rebuild -f -w better-sqlite3"
}
}如果使用 electron-builder,也可以使用:
{
"scripts": {
"postinstall": "electron-builder install-app-deps"
}
}pnpm 10 如果提示构建脚本被忽略,可以执行:
pnpm approve-builds
pnpm rebuild better-sqlite3如果报错如下:
D:\Node\环境\npm.cmd run dev
> [email protected] dev
> electron-vite dev
vite v5.4.21 building SSR bundle for development...
✓ 5 modules transformed.
out/main/index.js 4.36 kB
✓ built in 138ms
build the electron main process successfully
-----
vite v5.4.21 building SSR bundle for development...
✓ 1 modules transformed.
out/preload/index.js 1.31 kB
✓ built in 20ms
build the electron preload files successfully
-----
dev server running for the electron renderer process at:
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
start electron app...
[鍒濆鍖栨暟鎹簱]: 鏁版嵁搴撳湴鍧€锛欶:\椤圭洰\SQLPetera-Test 鏁版嵁搴撳悕锛歵est.db 鏄惁鎵撳嵃鏃ュ織锛氭槸
(node:20460) UnhandledPromiseRejectionWarning: Error: The module '\\?\F:\茅隆鹿莽聸庐\SQLPetera-Test\node_modules\better-sqlite3\build\Release\better_sqlite3.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 127. This version of Node.js requires
NODE_MODULE_VERSION 119. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
at process.func [as dlopen] (node:electron/js2c/node_init:2:2214)
at Module._extensions..node (node:internal/modules/cjs/loader:1356:18)
at Object.func [as .node] (node:electron/js2c/node_init:2:2214)
at Module.load (node:internal/modules/cjs/loader:1126:32)
at Module._load (node:internal/modules/cjs/loader:967:12)
at c._load (node:electron/js2c/node_init:2:13672)
at Module.require (node:internal/modules/cjs/loader:1150:19)
at require (node:internal/modules/cjs/helpers:119:18)
at bindings (F:\椤圭洰\SQLPetera-Test\node_modules\bindings\bindings.js:112:48)
at new Database (F:\椤圭洰\SQLPetera-Test\node_modules\better-sqlite3\lib\database.js:48:64)
(Use `electron --trace-warnings ...` to show where the warning was created)
(node:20460) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)请执行如下命令:
npm i -D @electron/rebuild
npx electron-rebuild -f -w better-sqlite3配置 TypeScript
tsconfig.json 需要开启装饰器:
如果是electron项目的话最好需要在
tsconfig.node.json中也添加配置
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}装饰器
标记表名@table
说明:
@table("test_table")表示实体对应数据库表test_table。- 如果没有使用
@table,默认使用实体类名作为表名。
使用
import { table } from "../Petra";
@table("test_table")
export class TestTable {
}标记字段属性@column
参数
| 参数 | 类型 | 默认值 | 说明 |
| -------------- | -------------------------------------------------- | ----------- | ----------------- |
| fieldName | string | 当前属性名 | 数据库字段名 |
| fieldType | "text" | "integer" | "real" | "blob" | "numeric" | 自动推断 | SQLite 字段类型 |
| isPrimaryKey | boolean | false | 是否主键 |
| isNull | boolean | false | 是否允许为 null |
| defaultValue | string | number | null | undefined | undefined | 建表时的默认值 |
| IsIgnore | boolean | false | 是否忽略该属性 |
使用示例
import { column, table } from "../Petra";
@table("test_table")
export class TestTable {
@column({ fieldName: "table_id", isPrimaryKey: true, isNull: true })
id?: number;
@column({ isNull: true })
name?: string;
@column({ isNull: false })
note?: string;
@column({ IsIgnore: true })
age?: number;
}字段类型推断说明
| TypeScript 类型 | SQLite 类型 |
| --------------- | ----------- |
| number | integer |
| string | text |
| boolean | integer |
| Date | text |
| Buffer | blob |
| 其它或无法识别 | text |
条件方法
该条件方法可用于查询、修改、删除 链式方法中
方法总览
| 方法名 | 参数 | 备注 |
| ------------ | ------------------------------------ | -------------------------------------------------------- |
| Where | (key, value, operator?) | where条件 |
| WhereIF | (haveValue, key, value, operator?) | where条件,如果表达式为false则不触发查询条件 |
| BetweenAnd | (key, begin, end) | 查询某个范围内的数据,比如 ID 范围、价格范围、时间范围 |
| Or | () | 把下一条条件和上一条条件用 OR 连接 |
| In | (key, values, type?) | 在给定的这个范围内查询 |
| Like | (key, value, type?) | 模糊匹配 |
类型总览
// 用于Where和WhereIF方法中的operator中
type WhereOperator = "=" | ">" | "<" | ">=" | "<=";
// 用于In方法中的type
type InType = "IN" | "NOT IN"
// 一般用于Like中的type方法
type LikeType = "left"|"right"|"all"Where条件
字段描述
key:该字段是给定类型中的字段value:条件语句的值,需要查询的条件值operator:类型条件,类型请看WhereOperator类型
数据库中做什么
生成 WHERE 字段 = @参数、WHERE 字段 > @参数 等条件;value 为 null 时生成 IS NULL
是什么意思 / 干嘛用
最常用的筛选条件,用来指定某个字段必须等于、大于、小于某个值
WhereIF条件
字段描述
haveValue:是否生效该条件key:该字段是给定类型中的字段value:条件语句的值,需要查询的条件值operator:类型条件,类型请看WhereOperator类型
数据库中做什么
haveValue 为 true 时才生成一段 WHERE 条件
是什么意思 / 干嘛用
用来写动态查询,比如搜索框有值才按关键字过滤,没有值就不过滤
BetweenAnd条件
字段描述
key:该字段是给定类型中的字段begin:其实条件值end:结束条件值
数据库中做什么
生成 字段 BETWEEN @begin AND @end
是什么意思 / 干嘛用
查询某个范围内的数据,比如 ID 范围、价格范围、时间范围
Or条件
数据库中做什么
把下一条条件和上一条条件用 OR 连接
是什么意思 / 干嘛用
表示“或者”,比如名字是张三或者李四都查出来
In条件
字段描述
key:该字段是给定类型中的字段values:条件语句的值,需要查询的条件值,类型是数组type:是否在当前范围做筛选还是在非范围内做筛选,类型请看InType
数据库中做什么
生成 字段 IN (...) 或 字段 NOT IN (...)
是什么意思 / 干嘛用
批量匹配多个值,比如查询 ID 在 [1,2,3] 中的数据,或排除这些 ID
Like条件
字段描述
key:该字段是给定类型中的字段value:条件语句的值,需要查询的条件值type:是左匹配还是右匹配还是全匹配,类型请看LikeType
数据库中做什么
生成 字段 LIKE @参数
是什么意思 / 干嘛用
模糊搜索文本,比如按姓名、标题、备注中的关键字查数据
查询数据
查询一条数据-Where
const item = Db.Queryable<TestTable>(TestTable).Where("name", "张三").First<TestTable>();查询所有数据-ToList
const list = Db.Queryable<TestTable>(TestTable).ToList<TestTable>();分页查询数据-ToPageList
字段描述
pageIndex:当前查询第几页pageSize:每一页的数据量大小
使用示例
const pageListDatas = Db.Queryable<TestTable>(TestTable).ToPageList<TestTable>(1,20);返回类型说明
| 字段 | 类型 | 说明 |
| ------------ | -------- | ------------------------- |
| pageIndex | number | 当前页数 |
| pageSize | number | 每页大小 |
| pages | number | 总页数 |
| total | number | 总条数 |
| dataSource | T | 数据,T这里是给定的类型 |
查询指定字段-SelectField
const names = Db.Queryable<TestTable>(TestTable).SelectField("name").ToList<string>();查询字段并设置别名-Select
type TestTableView = {
tableId?: number;
userName?: string;
};
const list = Db.Queryable<TestTable>(TestTable)
.Select<TestTableView>(x => ({
tableId: x.id,
userName: x.name
}))
.ToList<TestTableView>();分组-GroupBy和Having
const list = Db.Queryable<TestTable>(TestTable)
.GroupBy(["name"])
.Having("SUM", "id", 1, ">")
.ToList<{ name?: string }>();GroupBy(keys) 会生成 GROUP BY,并把查询字段重置为分组字段。Having(type, key, value, operator?) 当前支持 "SUM" 和 "AVG",会生成类似 SUM(table_id) > @having_table_id_0 的条件。
聚合方法
聚合方法说明:
| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 |
| ---------- | ------------------------------------ | ------------------------------------------------------------ | --------- |
| Count() | 执行 SELECT COUNT(1) | 统计符合条件的数据有多少条 | number |
| Sum(key) | 执行 SELECT SUM(字段) | 对某个数字字段求总和,比如统计金额合计、数量合计 | number |
| Avg(key) | 执行 SELECT AVG(字段) | 对某个数字字段求平均值,比如平均分、平均价格 | number |
| Max(key) | 执行 SELECT MAX(字段) | 查询某个字段的最大值,比如最大 ID、最高价格 | number |
| Min(key) | 执行 SELECT MIN(字段) | 查询某个字段的最小值,比如最小 ID、最低价格 | number |
| Any() | 执行 COUNT(1) 并判断数量是否大于 0 | 判断数据库里是否存在符合条件的数据,常用于重复校验、存在性判断 | boolean |
注意:如果聚合查询因为 GroupBy 返回多行,当前实现会返回 0 或 true,不返回分组后的聚合列表。
const count = Db.Queryable<TestTable>(TestTable).Count();
const sum = Db.Queryable<TestTable>(TestTable).Sum("id");
const avg = Db.Queryable<TestTable>(TestTable).Avg("id");
const max = Db.Queryable<TestTable>(TestTable).Max("id");
const min = Db.Queryable<TestTable>(TestTable).Min("id");
const any = Db.Queryable<TestTable>(TestTable).Where("name", "张三").Any();新增数据
方法说明
ExecuteCommand()返回影响行数,类型为number。- 批量新增会复用同一个 prepared statement。
- 批量新增如果不在外层事务中,会自动使用内部事务。
- 批量新增如果在
Db.Transaction()内,会复用外层事务。 - 空数组会直接返回
0。 - 新增时会按实体字段统一生成 INSERT,未传入字段会以
undefined作为参数值。
新增方法说明:
| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 |
| ----------------------------- | -------------------------------------- | ------------------------------------------------------------ | --------------- |
| Db.Insertable(Entity, data) | 准备 INSERT INTO 表(...) VALUES(...) | 告诉 ORM 要往哪个表插入什么数据,data 可以是一条对象或对象数组 | Insertable<T> |
| ExecuteCommand() | 真正执行 INSERT SQL | 前面的 Insertable 只是构造新增操作,调用它才会真正写入数据库 | number |
新增单条数据
const changes = Db.Insertable<TestTable>(TestTable, {
id: 1,
name: "张三",
note: "第一条数据"
}).ExecuteCommand();批量新增数据
const changes = Db.Insertable<TestTable>(TestTable, [
{ id: 2, name: "张三", note: "备注" },
{ id: 3, name: "李四", note: "备注" }
]).ExecuteCommand();修改数据
方法说明
| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 |
| ----------------------------------------------- | ---------------------------- | ---------------------------------------------------------- | --------------- |
| Db.Updateable(Entity) | 准备 UPDATE 表 SET ... | 告诉 ORM 接下来要修改哪个实体对应的数据库表 | Updateable<T> |
| SetColumns(columns) | 生成多个 SET 字段 = @参数 | 一次设置多个要修改的列,适合表单保存、批量字段更新 | this |
| SetColumnFiled(field, value) | 生成单个 SET 字段 = @参数 | 只修改某一列,field 可传实体属性名,会映射到数据库字段名 | this |
| Where / WhereIF / BetweenAnd / Or / In / Like | 生成 WHERE 条件 | 限制更新哪些行,避免把整张表都改掉 | this |
| AllowAll() | 允许没有 WHERE 的 UPDATE | 明确告诉 ORM:这次就是要更新整张表 | this |
| ExecuteCommand() | 真正执行 UPDATE SQL | 前面的链式方法只是构造更新语句,调用它才会提交修改 | number |
注意:
- 不加条件执行更新会抛出
请给定条件更新。 - 没有设置任何更新字段会抛出
请给定要更新的列。 SetColumnFiled方法名目前保留了历史拼写。
更新多个字段
const changes = Db.Updateable<TestTable>(TestTable)
.SetColumns({
id: 1,
name: "新名称",
note: "新备注"
})
.Where("id", 1)
.ExecuteCommand();更新单个字段
const changes = Db.Updateable<TestTable>(TestTable)
.SetColumnFiled("name", "新名称")
.Where("id", 1)
.ExecuteCommand();全表更新
默认不允许无条件更新。如果确实要更新全表,必须显式调用 AllowAll():
const changes = Db.Updateable<TestTable>(TestTable)
.SetColumnFiled("note", "全部修改")
.AllowAll()
.ExecuteCommand();删除数据
方法说明
| 方法 | 数据库中做什么 | 是什么意思 / 干嘛用 | 返回 |
| ----------------------------------------------- | ---------------------------- | ------------------------------------------------------ | --------------- |
| Db.Deleteable(Entity) | 准备 DELETE FROM 表 | 告诉 ORM 接下来要删除哪个实体对应表里的数据 | Deleteable<T> |
| Where / WhereIF / BetweenAnd / Or / In / Like | 生成 WHERE 条件 | 限制删除哪些行,常见用法是按主键、状态或 ID 列表删除 | this |
| AllowAll() | 允许没有 WHERE 的 DELETE | 明确告诉 ORM:这次就是要清空整张表数据 | this |
| ExecuteCommand() | 真正执行 DELETE SQL | 前面的链式方法只是构造删除语句,调用它才会真正删除数据 | number |
注意:不加条件执行删除会抛出 请给定条件删除。
按条件删除
const changes = Db.Deleteable<TestTable>(TestTable)
.Where("id", 1)
.ExecuteCommand();批量删除
const changes = Db.Deleteable<TestTable>(TestTable)
.In("id", [1, 2, 3], "IN")
.ExecuteCommand();全表删除
默认不允许无条件删除。如果确实要删除全表,必须显式调用 AllowAll():
const changes = Db.Deleteable<TestTable>(TestTable)
.AllowAll()
.ExecuteCommand();使用事务
使用Transaction
说明
- 回调执行成功时自动提交,并返回
{ isSuccess: true, message: "事务操作成功" }。 - 回调中抛出异常时自动回滚,并返回
{ isSuccess: false, message: "事务操作失败:..." }。 - 当前实现会捕获异常并返回失败结果,不会继续向外抛出原始异常。
使用示例
const result = Db.Transaction(() => {
Db.Insertable<TestTable>(TestTable, {
id: 10,
name: "事务新增",
note: "备注"
}).ExecuteCommand();
Db.Updateable<TestTable>(TestTable)
.SetColumnFiled("note", "事务修改")
.Where("id", 10)
.ExecuteCommand();
});
console.log(result.isSuccess, result.message);Db.Transaction(callback) 返回:
type TransActionReturnType = {
isSuccess: boolean;
message: string;
};执行原生 SQL
当链式 API 不满足需求时,可以直接执行自定义 SQL。
查询
const rows = Db.RunSQLQueryable(
"SELECT table_id, name FROM test_table WHERE table_id = @id",
{ id: 1 }
);新增
const changes = Db.RunSQLInsertable(
"INSERT INTO test_table(table_id, name, note) VALUES (@id, @name, @note)",
{ id: 20, name: "王五", note: "原生 SQL 新增" }
);更新
const changes = Db.RunSQLUpdateable(
"UPDATE test_table SET name = @name WHERE table_id = @id",
{ id: 20, name: "赵六" }
);删除
const changes = Db.RunSQLDeleteable(
"DELETE FROM test_table WHERE table_id = @id",
{ id: 20 }
);建议仍然使用命名参数,不要把用户输入直接拼接进 SQL 字符串。
扩展方法
关闭数据库
Db.Close();创建或更新表结构
行为说明:
- 表不存在时,根据实体上的
@column信息创建表。 - 表已存在时,会检查实体中新增的字段,并执行
ALTER TABLE ADD COLUMN。 - 当前不会自动删除数据库中多出来的字段。
- SQLite 不支持给已有表直接追加主键字段,遇到这种情况会抛出错误。
Db.CreateTableByEntity<TestTable>(TestTable);Electron渲染进程使用
因为封装的当前依赖是不可以在渲染进程使用的,所以我做了一个建议版本的操作数据库链式方法,可以跟着我如下操作你的electron项目
当前演示项目是
electron-vite脚手架创建的项目
首先我们需要实现一下我们的新增、修改、重新、删除方法
我们在我们的src/renderer/src/DbHelp目录中新增一个dbHelp方法(没有当前DbHelp目录的话手工新增)
import {
getTableColumns,
getTableName,
QueryableBase,
SQLType,
PageListType,
InsertableBase,
TableFiledType,
DeleteableBase,
UpdateableBase
} from 'sql-petra'
/**
* 查询使用的链式方法
* @param tableEntity 表实体
* @constructor
*/
class QueryableHelpFun<T> extends QueryableBase<T> {
/**
* 查询一条数据
* @constructor
*/
async First<FirstT>(): Promise<FirstT | T | null> {
const sql: SQLType = this._GetFirstSQL()
const d: FirstT[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as FirstT[]
let SQLResult: FirstT | null = null
if (d.length > 0) SQLResult = d[0] as FirstT
// 如果有值,并且返回值就一个的话则返回list
if (this.data.select.length == 1 && SQLResult != null)
return SQLResult[this.data.select[0].field] as FirstT
return SQLResult
}
/**
* 根据条件查询全部
* @constructor
*/
async ToList<ToListT>(): Promise<ToListT[] | T[]> {
const sql: SQLType = this._GetToListSQL()
const d: ToListT[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as ToListT[]
let SQLResult: ToListT[] = [] as ToListT[]
if (d.length > 0) SQLResult = d as ToListT[]
if (this.data.select.length == 1 && SQLResult != null) {
return SQLResult.map((x: ToListT) => x[this.data.select[0].field]) as ToListT[]
}
return SQLResult
}
/**
* 分页查询
* @param pageIndex 当前页
* @param pageSize 每页数据量大小
* @constructor
*/
async ToPageList<PageListT>(
pageIndex: number,
pageSize: number
): Promise<PageListType<PageListT>> {
// 需要返回的数据
const returnData: PageListType<PageListT> = {
pageIndex: pageIndex,
pageSize: pageSize,
pages: 0,
total: 0,
dataSource: []
}
// 获取总条数的SQL语句
const countSQL: SQLType = this._GetCountSQL()
// 数量
const dataCount: object[] = (await window.api.sql(
countSQL.SQL,
'查询',
countSQL.data
)) as object[]
// 总条数
returnData.total = dataCount[0]['data_count']
// 计算总页数
if (returnData.total % pageSize == 0) {
returnData.pages = returnData.total / pageSize
} else {
returnData.pages = Math.floor(returnData.total / pageSize) + 1
}
// 查询具体的数据
const pageSelectSQL: SQLType = this._GetToPageListSQL(pageIndex, pageSize)
const d: PageListT[] = (await window.api.sql(
pageSelectSQL.SQL,
'查询',
pageSelectSQL.data
)) as PageListT[]
returnData.dataSource = d as PageListT[]
return returnData
}
/**
* 获取有多少条数据
*/
async Count(): Promise<number> {
const sql: SQLType = this._GetCountSQL()
const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
if (dataCount.length > 1) return 0
return dataCount[0]['data_count']
}
/**
* 汇总值
* @param key 需要汇总的字段
* @constructor
*/
async Sum<k extends keyof T>(key: k): Promise<number> {
const sql: SQLType = this._GetSumSQL(key)
const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
if (dataCount.length > 1) return 0
return dataCount[0]['data_sum']
}
/**
* 平均值
* @param key 需要平均的字段
* @constructor
*/
async Avg<k extends keyof T>(key: k): Promise<number> {
const sql: SQLType = this._GetAvgSQL(key)
const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
if (dataCount.length > 1) return 0
return dataCount[0]['data_avg']
}
/**
* 最大值
* @param key 需要求取最大值的字段
* @constructor
*/
async Max<k extends keyof T>(key: k): Promise<number> {
const sql = this._GetMaxSQL(key)
const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
if (dataCount.length > 1) return 0
return dataCount[0]['data_max']
}
/**
* 最小值
* @param key 需要求取最小值的字段
* @constructor
*/
async Min<k extends keyof T>(key: k): Promise<number> {
const sql = this._GetMinSQL(key)
const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
if (dataCount.length > 1) return 0
return dataCount[0]['data_min']
}
/**
* 是否存在数据
* @constructor
*/
async Any(): Promise<boolean> {
const sql = this._GetAnySQL()
const dataCount: object[] = (await window.api.sql(sql.SQL, '查询', sql.data)) as object[]
if (dataCount.length > 1) return true
return dataCount[0]['data_count'] > 0
}
}
/**
* 新增使用的链式方法
* @param tableEntity 表实体
* @param insertData 需要添加的数据
* @constructor
*/
class InsertableFun<T> extends InsertableBase<T> {
constructor(tableName: string, fields: TableFiledType[], insertDatas: T[]) {
super(tableName, fields, insertDatas)
}
/**
* 提交更新
* @constructor
*/
async ExecuteCommand(): Promise<number> {
if (this.insertDatas.length == 0) return 0
const sql: SQLType = this._GetInsertSQL()
const d = (await window.api.sql(sql.SQL, '新增', sql.data))
console.log(d);
return d;
}
}
/**
* 删除使用的链式方法
* @param tableEntity 表实体
* @constructor
*/
class DeleteableFun<T> extends DeleteableBase<T> {
/**
* 提交更新
* @constructor
*/
async ExecuteCommand(): Promise<number> {
// SQL语句
const sql: SQLType = this._GetDeleteSQL()
return (await window.api.sql(sql.SQL, '删除', sql.data)) as number
}
}
/**
* 更新数据使用链式方法
*/
export class UpdateableFun<T> extends UpdateableBase<T> {
/**
* 提交更新
* @constructor
*/
async ExecuteCommand(): Promise<number> {
// SQL语句
const sql = this._GetUpdateSQL()
return (await window.api.sql(sql.SQL, '修改', sql.data)) as number
}
}
type Constructor<T> = new (...args: any[]) => T
export const dbHelp = {
/**
* 查询使用的链式方法
* @param tableEntity 表实体
* @constructor
*/
Queryable<T>(tableEntity: Constructor<T>): QueryableHelpFun<T> {
return new QueryableHelpFun<T>(getTableName(tableEntity), getTableColumns(tableEntity))
},
/**
* 新增使用的链式方法
* @param tableEntity 表实体
* @param insertData 需要添加的数据
* @constructor
*/
Insertable<T>(tableEntity: Constructor<T>, insertData: T | T[]): InsertableFun<T> {
const tableName = getTableName(tableEntity)
const fields: TableFiledType[] = getTableColumns(tableEntity)
const insertDatas: T[] = Array.isArray(insertData) ? insertData : [insertData]
return new InsertableFun(tableName, fields, insertDatas)
},
/**
* 删除使用的链式方法
* @param tableEntity 表实体
* @constructor
*/
Deleteable<T>(tableEntity: Constructor<T>): DeleteableFun<T> {
const tableName = getTableName(tableEntity)
const fields: TableFiledType[] = getTableColumns(tableEntity)
return new DeleteableFun<T>(tableName, fields)
},
/**
* 修改使用的链式方法
* @param tableEntity 表实体
* @constructor
*/
Updateable<T>(tableEntity: Constructor<T>): UpdateableFun<T> {
const tableName = getTableName(tableEntity)
const fields: TableFiledType[] = getTableColumns(tableEntity)
return new UpdateableFun(tableName, fields)
}
}这里使用了渲染进程与主进程通信,所以我们还需要新增以下的方法
首先需要定义接口
需要在src/preload/index.d.ts中定义
import { ElectronAPI } from '@electron-toolkit/preload'
import { IgnoreMouseEventsOptions } from "electron";
declare global {
interface Window {
electron: ElectronAPI
// 定义一下即可有提示了
api: {
/**
* 执行SQL语句
* @param sql SQL语句
* @param type 类型
* @param args 参数
*/
sql: (sql: string, type: '查询' | '修改' | '删除' | '新增', args?: Record<string, any>) => Promise<any>
}
}
}需要在我们的src/preload/index.ts中实现发送消息的通知
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const api = {
/**
* 执行SQL语句
* @param sql SQL语句
* @param type 类型
* @param args 参数
*/
sql: (sql: string, type: '查询' | '修改' | '删除' | '新增', args?: Record<string, any>): Promise<any> => {
return new Promise((resolve, reject) => {
try {
ipcRenderer
.invoke('sql', sql, type, args)
.then((result) => {
resolve(result as any)
})
.catch((err) => {
reject(err)
})
} catch (e) {
reject(e)
}
})
},
}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
// @ts-ignore (define in dts)
window.electron = electronAPI
// @ts-ignore (define in dts)
window.api = api
}在src/main/ipc.ts中定义执行逻辑的方法(这里的ipc需要在src/main/index.ts中引入)
// 主进程的main中引入
import './ipc'import { ipcMain, IpcMainInvokeEvent } from "electron";
import { Db } from "sql-petra-orm";
/**
* 操作SQL语句
*/
ipcMain.handle("sql",(event: IpcMainInvokeEvent, sql: string, type: '查询' | '修改' | '删除' | '新增', args?: Record<string, any>)=>{
if (event) {
switch (type) {
case '查询':
return Db.RunSQLQueryable(sql, args)
case '修改':
return Db.RunSQLUpdateable(sql, args)
case '删除':
return Db.RunSQLUpdateable(sql, args)
case '新增':
return Db.RunSQLInsertable(sql, args)
}
}
})使用示例
<script setup lang="ts">
import { onMounted } from "vue";
import { dbHelp } from "@renderer/DbHelp/DbHelp";
import { tn_sys_user } from "../../types/tn_sys_user";
onMounted(async ()=>{
const userCount:number = await dbHelp.Queryable<tn_sys_user>(tn_sys_user)
.Where("cn_s_user_name","张三")
.Count();
const d:tn_sys_user = {
cn_s_user_id:"2",
cn_s_user_name:"李四",
cn_s_user_sex:"男"
}
const insertCount = await dbHelp.Insertable<tn_sys_user>(tn_sys_user,d)
.ExecuteCommand();
console.log("张三的用户数:",userCount);
console.log("新增李四信息是否成功:",insertCount >= 1);
})
</script>
<template>
<div>
测试SQLORM
</div>
</template>备份数据库
- 备份底层调用
better-sqlite3的backup。 - 建议在数据库初始化之后调用。
- 如果需要严格等待备份完成,建议后续把
Db.Backup的对外类型调整为Promise<boolean>。
使用示例
Db.Backup("F:\\项目\\SQLPetra\\backup.db");完整示例
import { Db, table, column } from "sql-petra-orm";
@table("test_table")
class TestTable {
@column({ fieldName: "table_id", isPrimaryKey: true })
id?: number;
@column({ isNull: true })
name?: string;
@column({ isNull: true })
note?: string;
}
Db.InitDb({
dbPath: "F:\\项目\\SQLPetra\\",
dbName: "test.db",
isWriteLog: true
});
Db.CreateTableByEntity<TestTable>(TestTable);
const tx = Db.Transaction(() => {
Db.Insertable<TestTable>(TestTable, [
{ id: 1, name: "张三", note: "第一条数据" },
{ id: 2, name: "李四", note: "第二条数据" }
]).ExecuteCommand();
Db.Updateable<TestTable>(TestTable)
.SetColumnFiled("note", "已修改")
.Where("id", 1)
.ExecuteCommand();
});
if (!tx.isSuccess) {
console.error(tx.message);
}
const list = Db.Queryable<TestTable>(TestTable)
.Select<{ tableId?: number; userName?: string }>(x => ({
tableId: x.id,
userName: x.name
}))
.WhereIF(true, "name", "张三")
.ToList<{ tableId?: number; userName?: string }>();
console.log(list);
Db.Close();