事务 @Transactional
ACE ORM 支持两种事务风格:声明式(@Transactional 宏织入)和编程式(ds.transaction{})。两者最终都在 ThreadLocal 上下文中维护同一连接,保证同一协程内的所有操作走同一事务。
声明式事务 @Transactional
在 @Service 方法上标注 @Transactional,宏在编译期生成 begin/commit/rollback 包裹代码:
package demo.service
import ace_framework.*
import ace_orm.*
import ace_orm.macros.*
import demo.model.*
@Service
public class OrderService {
@Inject
var orderRepo: OrderRepository
@Inject
var itemRepo: OrderItemRepository
@Transactional
public func placeOrder(userId: Int64, items: Array<Item>): Int64 {
let order = Order()
order.userId = userId
order.status = "PENDING"
let orderId = orderRepo.insert(order)
for (item in items) {
let oi = OrderItem()
oi.orderId = orderId
oi.productId = item.productId
oi.qty = item.qty
itemRepo.insert(oi)
}
return orderId
// 正常返回 → 自动 COMMIT
}
// 任意 Exception → 自动 ROLLBACK,异常继续向上传播
}零反射
@Transactional 在编译期生成等价代码,运行时无代理/反射开销。可通过 --debug-macro 审计生成内容。
编程式事务 ds.transaction{}
当无法在方法上标注宏(如动态流程、循环中的批量操作)时,使用 ds.transaction{} 显式划定事务边界:
import ace_orm.*
func batchImport(ds: DataSource, rows: Array<ImportRow>): Unit {
ds.transaction {
for (row in rows) {
// 所有 insert 在同一事务
let entity = toEntity(row)
repo.insert(entity)
}
// Lambda 正常退出 → COMMIT
}
// Lambda 内抛出 Exception → ROLLBACK,异常重新抛出
}带返回值:
let total: Int64 = ds.transaction {
repo.sum("amount", [eq("month", DbInt(7))])
}事务感知机制
ACE ORM 通过 ThreadLocal<Connection> 维护事务上下文。每个协程有独立的上下文,同一协程内嵌套调用共享同一连接。
协程 A: begin → op1 → op2 → commit ← 同一连接
协程 B: begin → op3 → rollback ← 独立连接跨协程不共享事务
spawn{ } 创建的子协程运行在独立的 OS 线程调度环境,不继承父协程的事务上下文。若需要子协程参与父事务,必须显式传递 DataSource 并在子协程内单独开启事务。
嵌套 @Transactional(REQUIRED 传播)
多层 @Transactional 方法嵌套调用时,外层已开启事务,内层复用同一事务(不新建,不重复 begin):
@Transactional
public func createUserWithProfile(name: String, bio: String): Int64 {
let userId = userRepo.insert(User(name: name))
profileService.createProfile(userId, bio) // 内层同事务
return userId
}
// ProfileService
@Transactional
public func createProfile(userId: Int64, bio: String): Unit {
profileRepo.insert(Profile(userId: userId, bio: bio))
// 内层正常返回 → 无独立 COMMIT(由外层控制)
// 内层抛出 → 外层感知并 ROLLBACK 整体
}EntityManager — 跨实体事务
EntityManager 在同一事务中操作多个不同实体类型,避免手动协调多个 Repository:
import ace_orm.*
func transferCredits(ds: DataSource, fromId: Int64, toId: Int64, amount: Int64): Unit {
ds.transaction {
let em = EntityManager(ds)
// 操作不同实体类型,共享同一事务连接
em.exec("UPDATE accounts SET balance = balance - ? WHERE id = ?",
[DbInt(amount), DbInt(fromId)])
em.exec("UPDATE accounts SET balance = balance + ? WHERE id = ?",
[DbInt(amount), DbInt(toId)])
em.exec("INSERT INTO transfer_logs (fromId, toId, amount, ts) VALUES (?, ?, ?, ?)",
[DbInt(fromId), DbInt(toId), DbInt(amount), DbInt(nowMs)])
}
}EntityManager 的常用方法:
let em = EntityManager(ds)
// 裸 SQL 执行
em.exec(sql: String, params: Array<DbValue>): Int64
// 裸 SQL 查询
em.query(sql: String, params: Array<DbValue>): Array<Row>
// 获取 QueryRunner(支持 DDL 助手)
em.queryRunner(): QueryRunner悲观锁(SELECT FOR UPDATE)
在事务内使用 QueryBuilder 悲观锁防止并发修改:
@Transactional
public func deductStock(productId: Int64, qty: Int64): Unit {
let product = productRepo
.createQueryBuilder("p")
.andWhere("p.id", "=", DbInt(productId))
.forUpdate() // 锁定行,其他事务阻塞直到本事务结束
.getOne()
.getOrThrow()
if (product.stock < qty) {
throw OrmException("库存不足", code: "STOCK_ERROR")
}
product.stock -= qty
productRepo.update(product)
}SQLite 退化
SQLite 不支持行锁,forUpdate() 在 SQLite 上退化为无锁(无 FOR UPDATE 子句)。并发安全请切换 PostgreSQL 或使用应用层互斥。
乐观锁(@VersionColumn)
@VersionColumn 在无并发争用时性能更好,通过版本号检测冲突(CAS 语义):
@Entity["products"]
public class Product {
@Id[]
public var id: Int64 = 0
@Column[]
public var stock: Int64 = 0
@VersionColumn[]
public var version: Int64 = 0 // 每次 update 自动 +1
}使用:
let product = productRepo.findById(DbInt(1)).getOrThrow()
product.stock -= qty
// 内部生成: UPDATE products SET stock = ?, version = version + 1
// WHERE id = ? AND version = ?
// 受影响行 = 0 则抛 OptimisticLockException(可捕获后重试)
productRepo.update(product)回滚条件速查
| 场景 | 行为 |
|---|---|
@Transactional 方法正常返回 | 自动 COMMIT |
@Transactional 方法抛出任意 Exception | 自动 ROLLBACK,异常继续向上 |
ds.transaction {} Lambda 正常退出 | 自动 COMMIT |
ds.transaction {} Lambda 抛出 Exception | 自动 ROLLBACK,异常重新抛出 |
嵌套 @Transactional 内层正常返回 | 不独立 COMMIT(由外层控制) |
嵌套 @Transactional 内层抛出异常 | 标记整体事务为回滚,外层感知并 ROLLBACK |
API 速查
| 方式 | 说明 |
|---|---|
@Transactional | 声明式,编译期织入,REQUIRED 传播 |
ds.transaction { } | 编程式,手动划定事务边界 |
EntityManager(ds) | 跨实体操作,共享当前事务连接 |
qb.forUpdate() | 悲观写锁(PG/MySQL) |
qb.forShare() | 悲观读锁(PG/MySQL) |
@VersionColumn[] | 乐观锁,冲突抛 OptimisticLockException |