Skip to content

事务 @Transactional

ACE ORM 的事务管理遵循「声明优先、零模板」原则:在方法上标注 @Transactional,框架在编译期生成 begin / commit / rollback 的包裹代码,运行时无反射开销。

基础用法

标注 @Transactional

cangjie
package demo.service

import ace.framework.*
import ace.orm.*

@Service
public class OrderService {
    @Inject
    private let repo: OrderRepository

    @Transactional
    public func placeOrder(userId: Int64, items: Array<Item>): Order {
        let order = repo.save(Order(userId: userId, status: "PENDING"))
        for item in items {
            repo.saveItem(OrderItem(orderId: order.id, itemId: item.id))
        }
        // 方法正常返回 → 自动 commit
        return order
    }
}

方法内任意位置抛出 Exception,框架自动执行 ROLLBACK,异常继续向上传播。

传播行为

ACE 支持两种传播行为,通过不同注解选择。

注解传播行为说明
@TransactionalREQUIRED(默认)复用调用栈上已有的事务;若无则新建
@RequiresNewREQUIRES_NEW始终新建独立事务(共享连接时建 savepoint)

REQUIRED — 加入已有事务

cangjie
@Transactional
public func outer(): Unit {
    inner()   // inner 加入 outer 的事务,共享同一连接
}

@Transactional
public func inner(): Unit {
    repo.doSomething()
}

outer 提交或回滚时,inner 的操作一并处理。

@RequiresNew — 独立事务 / Savepoint

cangjie
@Transactional
public func outer(): Unit {
    repo.stepA()
    auditService.log("step-a")  // @RequiresNew,独立提交
    repo.stepB()
}

@Service
public class AuditService {
    @RequiresNew
    public func log(event: String): Unit {
        auditRepo.insert(AuditLog(event: event))
        // 即使 outer 最终回滚,本次 log 已独立提交
    }
}

Savepoint 机制

当外层事务持有的连接被内层 @RequiresNew 复用时,框架自动在该连接上执行 SAVEPOINT sp_N。内层回滚仅回退到 savepoint,不影响外层事务。

嵌套调用

多层 @Transactional 调用在同一协程内共享同一事务上下文(即同一数据库连接)。

cangjie
@Transactional
public func createUserWithProfile(req: CreateUserReq): User {
    let user = userRepo.save(User(name: req.name))
    profileService.create(user.id, req.profile)  // 同事务
    return user
}

// ProfileService
@Transactional
public func create(userId: Int64, profile: Profile): Unit {
    profileRepo.save(ProfileRecord(userId: userId, ...profile))
}

外层事务回滚时,内层操作也一并回滚。

perOp 模式说明

SQLite 默认采用 perOp 模式(每次数据库操作独立获取连接),此模式下:

  • @Transactional 仍然有效(同一方法内的操作在同一连接上执行事务)。
  • @RequiresNew 的 savepoint 无回滚隔离效果,因为内外层实际使用不同连接。

SQLite perOp 限制

使用 SQLite 时,若需要 @RequiresNew 的独立回滚语义,请将连接池配置为 poolMode = "shared" 或切换到支持连接复用的数据库(如 PostgreSQL)。

toml
# cjpm.toml 或 ace.toml
[database]
url      = "sqlite://./data.db"
poolMode = "shared"   # 启用连接复用,savepoint 才生效

手动事务

不方便使用注解时(例如动态构建多步操作),可使用 withTransaction Lambda 形式:

cangjie
import ace.orm.withTransaction

public func batchImport(rows: Array<Row>): Unit {
    withTransaction {
        for row in rows {
            repo.insert(row)
        }
        // Lambda 正常返回 → commit
        // Lambda 内抛出异常 → rollback
    }
}

withTransaction 的返回值与 Lambda 返回值类型一致:

cangjie
let total: Int64 = withTransaction {
    repo.sumAll()
}

回滚条件

场景行为
方法正常返回自动 COMMIT
方法抛出任意 Exception自动 ROLLBACK
withTransaction Lambda 正常退出自动 COMMIT
withTransaction Lambda 抛出异常自动 ROLLBACK,异常重新抛出

@Async 与事务

跨协程事务不共享

@Async 方法在独立协程中运行,无法继承调用方的事务上下文。在 @Async 方法内调用带 @Transactional 的操作时,会开启一个全新事务,与外层事务完全隔离。

cangjie
@Transactional
public func outer(): Unit {
    repo.stepA()
    asyncService.doSomething()   // 在新协程中,独立事务!
    repo.stepB()
    // stepA 和 stepB 在同一事务;doSomething 在独立事务
}

@Service
public class AsyncService {
    @Async
    @Transactional
    public func doSomething(): Unit {
        repo.asyncStep()
    }
}

如需跨异步边界协调事务,请在 @Async 方法的返回值 Future 上使用 await 后再处理提交逻辑,或将异步调用移至事务边界之外。

API 速查

注解 / 函数位置说明
@Transactional方法REQUIRED 传播,自动 begin/commit/rollback
@RequiresNew方法REQUIRES_NEW 传播,建立独立事务或 savepoint
withTransaction { }调用处手动划定事务边界,Lambda 作为事务体

基于 Apache-2.0 许可证发布