事务
用 sqlClient.execute(...) 把一段工作包进事务。四种调用形态:
await sqlClient.execute(async () => { ... }) // 默认(REQUIRED)
await sqlClient.execute("REQUIRES_NEW", async () => { ... }) // 传播级别
await sqlClient.execute("SERIALIZABLE", async () => { ... }) // 隔离级别
await sqlClient.execute({ propagation, isolation, timeout }, async () => { ... }) // 全选项
事务块里返回的值会原样透出(execute 返回 fn 的返回值),异常时回滚。
传播级别(Propagation)
| 值 | 语义 |
|---|---|
REQUIRED(默认) | 已有事务则加入,没有则新建 |
REQUIRES_NEW | 总是挂起外层、开一个新事务 |
NESTED | 嵌套事务(保存点) |
NOT_SUPPORTED | 挂起外层事务、无事务执行 |
NEVER | 已有事务则抛错 |
MANDATORY | 必须已有事务,否则抛错 |
隔离级别(Isolation)
READ_UNCOMMITTED / READ_COMMITTED(PG 默认)/ REPEATABLE_READ / SERIALIZABLE——语义与数据库标准一致。
沙盒里直接可用
示例中的 sqlClient 已带事务管理器,事务内跑查询完全没问题:
// ① 模型(完整可运行版见工作区)
import { model, prop, dto } from '@ts-grm/core'
import { sqlClient } from './infra/sql-client'
const Book = model("Book", "id", class {
id = prop.i64()
name = prop.str(50)
edition = prop.i32()
price = prop.num(10, 2)
})
const BookView = dto.view(Book, c => [c.id, c.name, c.price])
// ② 默认事务:把一组查询放进同一个事务
const rows = await sqlClient.execute(async () => {
const a = await sqlClient.createQuery(Book, (q, b) => q.select(b.fetch(BookView))).fetchList()
const n = await sqlClient.createQuery(Book, (q, b) => {
q.where(b.price.gt(50))
return q.select(b.fetch(BookView))
}).fetchCount()
return { all: a.length, expensive: n }
})
console.log(rows) // { all: 3, expensive: 2 }
// ③ 显式选项:新事务 + 可重复读 + 超时(毫秒)
const isolated = await sqlClient.execute({
propagation: "REQUIRES_NEW",
isolation: "REPEATABLE_READ",
timeout: 5000, // 毫秒
}, async () => {
return (await sqlClient.createQuery(Book, (q, b) => q.select(b.fetch(BookView))).fetchList()).length
})
console.log('REQUIRES_NEW:', isolated)
注意事项
- 只读事务也是事务:变更 API(保存/删除)尚未发布,事务内目前以读为主;未来
save落地后同一execute包裹即拿到原子性; - 事务块内再次
execute按传播级别处理(默认 REQUIRED = 并入外层); - 连接来自驱动连接池,事务按需获取/释放,无需手工 begin/commit。