TSts-grm

事务

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。