事务处理
VEF Framework 提供了简洁的事务处理 API,确保数据一致性。
基本事务
使用 Transaction 函数
go
import "github.com/ilxqx/vef-framework-go/orm"
err := orm.Transaction(func(tx *gorm.DB) error {
// Create user
user := &models.User{
Username: "john",
Email: "john@example.com",
}
if err := tx.Create(user).Error; err != nil {
return err // Rollback on error
}
// Create user profile
profile := &models.UserProfile{
UserId: user.Id,
Bio: "Hello, I'm John",
}
if err := tx.Create(profile).Error; err != nil {
return err // Rollback on error
}
return nil // Commit on success
})
if err != nil {
// Transaction failed
log.Error("Transaction failed:", err)
}手动事务控制
go
tx := orm.DB().Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// Perform operations
if err := tx.Create(&user).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&profile).Error; err != nil {
tx.Rollback()
return err
}
// Commit transaction
return tx.Commit().Error嵌套事务
使用 SavePoint
go
err := orm.Transaction(func(tx *gorm.DB) error {
// Main transaction operations
if err := tx.Create(&order).Error; err != nil {
return err
}
// Nested transaction with savepoint
err := tx.Transaction(func(tx2 *gorm.DB) error {
// This can be rolled back independently
if err := tx2.Create(&orderItem).Error; err != nil {
return err // Only rolls back to savepoint
}
return nil
})
if err != nil {
// Handle nested transaction failure
// Main transaction can still continue
}
return nil
})事务选项
设置隔离级别
go
import "database/sql"
tx := orm.DB().Begin(&sql.TxOptions{
Isolation: sql.LevelSerializable,
})只读事务
go
tx := orm.DB().Begin(&sql.TxOptions{
ReadOnly: true,
})在 API 中使用事务
自动事务
预置 API 的写入操作自动在事务中执行:
go
// CreateApi, UpdateApi, DeleteApi automatically use transactions
apis.NewCreateApi[models.User, payloads.UserParams]().
WithHook(api.BeforeCreate, validateUser). // In transaction
WithHook(api.AfterCreate, createUserProfile) // Same transaction自定义 API 中的事务
go
func (r *UserResource) TransferBalance(ctx fiber.Ctx, params TransferParams) error {
err := orm.Transaction(func(tx *gorm.DB) error {
// Deduct from sender
if err := tx.Model(&models.User{}).
Where("id = ? AND balance >= ?", params.FromUserId, params.Amount).
Update("balance", gorm.Expr("balance - ?", params.Amount)).Error; err != nil {
return err
}
// Check if deduction was successful
var fromUser models.User
if err := tx.First(&fromUser, "id = ?", params.FromUserId).Error; err != nil {
return errors.New("insufficient balance")
}
// Add to receiver
if err := tx.Model(&models.User{}).
Where("id = ?", params.ToUserId).
Update("balance", gorm.Expr("balance + ?", params.Amount)).Error; err != nil {
return err
}
// Create transfer record
transfer := &models.Transfer{
FromUserId: params.FromUserId,
ToUserId: params.ToUserId,
Amount: params.Amount,
CreatedAt: time.Now(),
}
if err := tx.Create(transfer).Error; err != nil {
return err
}
return nil
})
if err != nil {
return api.Error(ctx, err.Error())
}
return api.Success(ctx, nil)
}事务回调
注册提交后回调
go
err := orm.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&user).Error; err != nil {
return err
}
// Register callback to run after commit
tx.Callback().Create().After("gorm:after_create").Register("send_email", func(db *gorm.DB) {
// This runs after successful commit
go sendWelcomeEmail(user.Email)
})
return nil
})错误处理
检查特定错误
go
import "gorm.io/gorm"
err := orm.Transaction(func(tx *gorm.DB) error {
if err := tx.First(&user, "id = ?", userId).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("user not found")
}
return err
}
return nil
})自定义回滚
go
err := orm.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err
}
// Check business rule
if order.Amount > 10000 {
// Explicit rollback with custom error
return errors.New("order amount exceeds limit")
}
return nil
})最佳实践
- 保持事务简短:长事务会锁定资源,影响并发性能
- 避免在事务中进行外部调用:如 HTTP 请求、发送邮件等
- 使用适当的隔离级别:根据业务需求选择
- 处理死锁:实现重试机制
go
func executeWithRetry(fn func() error, maxRetries int) error {
var err error
for i := 0; i < maxRetries; i++ {
err = fn()
if err == nil {
return nil
}
// Check if it's a deadlock error
if isDeadlockError(err) {
time.Sleep(time.Millisecond * time.Duration(100*(i+1)))
continue
}
return err
}
return err
}