数据库访问手册(打印精灵内置 JS)

本文档面向打印精灵内置 JS 开发者,介绍脚本中数据库访问(DB 对象)的用法, 涵盖 ORM 查询构建器、事务、错误处理与连接池语义。

数据库函数具有广泛的用途,配合打印精灵的Hook函数, 如打印前获取订单的详细信息,打印后更新数据库的状态等等,可以实现各种复杂的业务), 如打印前获取订单的详细信息,打印后更新数据库的状态等等,流程:

例子: 订单获取和打印的例子:

1// updateVars()为钩子函数,每次打印前会自动调用
2function updateVars() {
3	let orderid= get("orderid")    //获取从API传递
4	let db = DB.Open("mysql:user:pass@tcp(host:port)db") //连接数据库
5	let data=db.Query(`select * from order where id={$orderid}`) 数据查询
6	return data[0]               //使用return 返回查询结果,会更新变量表
7}

该例子中,每次打印前会自动调用updateVars()函数,通过送入的orderid参数获取订单的详细信息,并打印。

虽然通过API送入完整的订单数据是一样的效果,但是更加灵活:API送入的参数只有一个,不需要修改打印程序,只修改标签模板,就可以实现不同产品的订单打印。

1. 支持的数据类型与 DSN

DSN 一律以 类型: 前缀开头(前缀大小写不敏感):

类型前缀 说明
sqlite: / sqlite3: SQLite 文件;相对路径基于内置 baseDir 展开
sqlite::memory: 内存库,每次 Open 都是独立数据库,不入池
mysql: MySQL,DSN 为 user:pass@tcp(host:port)/db
postgres: / postgresql: / pgsql: PostgreSQL,DSN 为键值连接串
mssql: / sqlserver: SQL Server,DSN 为 //user:pass@host:1433?database=db

不带冒号前缀的 DSN(或单字母盘符,如 C:\data.xlsx)会被当作 Excel 文件打开, 第二个参数为工作表名。

1const db = DB.Open("sqlite:data.db");
2const db2 = DB.Open("sqlite::memory:");
3const db3 = DB.Open("mysql:root:pwd@tcp(localhost:3306)/shop");

2. 快速开始

 1const db = DB.Open("sqlite:data.db");
 2
 3// 建表
 4db.Exec(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT,
 5        name TEXT, age INTEGER)`);
 6
 7// 插入并取回自增 id
 8const id = db.Table("users").Create({ name: "张三", age: 25 });
 9
10// 查询
11const rows = db.Table("users").Where("age > ?", 20).Order("age desc").Find();
12
13// 事务
14const r = db.Transaction(function (tx) {
15    tx.Table("users").Where("name = ?", "张三").Update({ age: 26 });
16    return { ok: true };   // 返回普通对象 → 提交;返回含 error 键的对象 → 回滚
17});
18if (r.error) { /* 失败 */ }
19
20db.Close();

3. 数据源开关

  • DB.Open(dsn[, sheet]) — 打开数据源,返回数据库对象;打开失败抛异常。
  • DB.Close(dsn[, sheet]) — 释放对应数据源(池内引用减一)。

打开后的对象也可通过 .Close() 关闭:

1const db = DB.Open("sqlite:data.db");
2// ... 使用
3db.Close();

DB.Close("sqlite:data.db")db.Close() 效果相同(引用计数减一),不要重复调用。

4. ORM 查询构建器

db.Table("表名") 返回查询构建器,方法均可链式调用。

方法 说明
Select(fields) 选择字段,如 "id, name";默认 SELECT *
Where(...) 追加 AND 条件
Or(...) 追加 OR 条件
Order(order) 排序,如 "age desc"
Limit(n) 限制行数
Offset(n) 偏移
Joins(joinSQL) 追加 JOIN 子句
PrimaryKey(col) 指定主键列(默认 id,影响 UpdateById/DeleteById

Where / Or 的写法

1// 1. 字符串 + 占位参数
2db.Table("users").Where("age > ?", 20)
3
4// 2. 单参数对象,键=列,值=等值条件
5db.Table("users").Where({ name: "张三" })
6
7// 3. 多条件叠加(AND)
8db.Table("users").Where("age > ?", 20).Where({ active: 1 })

Or 同理:

1db.Table("users").Where("age > ?", 30).Or("age < ?", 25)
2// 生成: WHERE (age > ?) OR (age < ?)

WHERE 与 OR 段会被自动加上括号,避免 a AND b OR c 这类优先级歧义。

分页

1const page2 = db.Table("users").Order("id").Limit(10).Offset(10).Find();

各驱动分页语法:

  • sqlite / mysql / postgres:LIMIT n OFFSET m;只设 Offset 时自动补齐无上限的 LIMIT。
  • mssql:自动转为 ORDER BY ... OFFSET m ROWS FETCH NEXT n ROWS ONLY(无 ORDER BY 时补 ORDER BY (SELECT NULL))。

5. 查询执行

方法 返回
Find() 数组,每行为 {列: 值}
First() 单行对象;无记录时报错
Count() 数字
Pluck(col) 某列的值数组
1const rows = db.Table("users").Where({ age: 25 }).Find();
2const one = db.Table("users").Where("id = ?", 1).First();
3const n = db.Table("users").Count();
4const names = db.Table("users").Pluck("name");

回调风格

Find/First/Count/Pluck 支持传入回调,出错时回调收到 (null, {error: 消息}), 不再抛异常:

1db.Table("users").Where("id = ?", 1).First(function (err, res) {
2    if (res.error) { console.log("查询失败:", res.error); return; }
3    console.log(res); // {id, name, age}
4});

不传回调时,出错会抛异常终止脚本。

6. 写操作

方法 返回 说明
Create(data) 自增 id 单行插入
CreateBulk(rows) 受影响行数 批量插入;行与首行字段不一致会报错
Update(data) 受影响行数 必须带 Where/Or,否则报错
UpdateById(id, data) 受影响行数 按主键更新(默认 id 列)
Delete() 受影响行数 必须带 Where/Or,否则报错
DeleteById(id) 受影响行数 按主键删除
 1db.Table("users").Create({ name: "李四", age: 30 });
 2db.Table("users").Where("name = ?", "李四").Update({ age: 31 });
 3db.Table("users").Where("id = ?", 1).Delete();
 4
 5// 非 "id" 主键
 6db.Table("users").PrimaryKey("uid").UpdateById(1, { age: 32 });
 7db.Table("users").PrimaryKey("uid").DeleteById(1);
 8
 9// 批量
10db.Table("users").CreateBulk([
11    { name: "王五", age: 22 },
12    { name: "赵六", age: 35 },
13]);

写操作出错一律抛异常。

7. 原生 SQL

1const affected = db.Exec("UPDATE users SET age = ? WHERE name = ?", 26, "张三");
2const rows = db.Query("SELECT * FROM users WHERE age > ?", 20);
  • Exec 返回受影响行数,Query 返回 [{列: 值}]
  • 出错抛异常。

8. 事务

db.Transaction(callback)

回调接收 tx(支持 .Table(),用法与 db.Table() 一致)。 返回值决定结果:

  • 返回不含 error 键的对象(或其它值)→ 提交。
  • 返回error 键的对象 → 回滚并原样返回该对象。
  • 回调内部抛异常 → 自动回滚并继续抛出。
1const r = db.Transaction(function (tx) {
2    tx.Table("accounts").Where("name = ?", "张三").Update({ balance: 90 });
3    if (balance < 0) return { error: "余额不足" };  // 回滚
4    return { ok: true };                            // 提交
5});
6if (r.error) { console.log(r.error); }

注意事项

  • 事务内 Count/Find 等查询走事务连接,能看到本事务未提交的数据。
  • 事务内不要使用 db.Exec/db.Query(它们是连接级接口,不走事务)。
  • 事务异常时保证回滚,不会悬挂连接。

9. 错误处理约定

场景 行为
DB.Open 失败 抛异常
查询(无回调)失败 抛异常
查询(带回调)失败 回调收到 {error: 消息}
First 无记录 报错 record not found
写操作失败 抛异常
原生 SQL 失败 抛异常
事务失败 返回 {error: 消息} 或抛异常

10. 连接池与关闭语义

  • 同一 DSN(类型 + 地址)共享一个底层连接池,使用引用计数管理。
  • Open 成功一次计数 +1;Close/db.Close() 一次计数 -1;归零时才真正关闭连接。
  • sqlite::memory: 例外:不入池,每次 Open 独立内存库,且限制单连接。
1// 同一文件打开两次,底层共享同一连接池
2const db1 = DB.Open("sqlite:data.db");
3const db2 = DB.Open("sqlite:data.db");
4db1.Close();          // 仍可用,db2 持有引用
5db2.Close();          // 引用归零,真正关闭

11. 驱动方言差异

  • 占位符:统一使用 ?。postgres 驱动会自动转换为 $1、$2…
  • 分页:mssql 自动转 OFFSET/FETCH;其余用 LIMIT/OFFSET
  • 主键UpdateById/DeleteById 默认用 id 列,可用 PrimaryKey() 指定。
  • SQLite 建库时自动创建文件所在目录。

留言

登录