Skip to content

事务 @Transactional

ACE ORM 支持两种事务风格:声明式@Transactional 宏织入)和编程式ds.transaction{})。两者最终都在 ThreadLocal 上下文中维护同一连接,保证同一协程内的所有操作走同一事务。


声明式事务 @Transactional

@Service 方法上标注 @Transactional,宏在编译期生成 begin/commit/rollback 包裹代码:

cangjie
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{} 显式划定事务边界:

cangjie
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,异常重新抛出
}

带返回值:

cangjie
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):

cangjie
@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:

cangjie
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 的常用方法:

cangjie
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 悲观锁防止并发修改:

cangjie
@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 语义):

cangjie
@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
}

使用:

cangjie
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

基于 Apache-2.0 许可证发布