乐闻世界logo
搜索文章和话题

How to use transactions in GORM?

3月6日 21:37

GORM provides powerful transaction support to ensure atomicity and consistency of multiple database operations.

Basic Transaction Operations

Automatic Transactions

GORM automatically manages transactions for single operations by default:

go
// Single operation automatically uses transaction db.Create(&user) db.Save(&user) db.Delete(&user)

Manual Transactions

For scenarios requiring multiple operations, manual transaction management is needed:

go
// Begin transaction tx := db.Begin() // Execute operations if err := tx.Create(&user).Error; err != nil { // Error occurred, rollback transaction tx.Rollback() return err } if err := tx.Create(&profile).Error; err != nil { tx.Rollback() return err } // Commit transaction tx.Commit()

Transaction Methods

Begin() - Start transaction

go
tx := db.Begin()

Commit() - Commit transaction

go
tx.Commit()

Rollback() - Rollback transaction

go
tx.Rollback()

RollbackTo() - Rollback to savepoint

go
// Create savepoint tx.SavePoint("sp1") // Rollback to savepoint tx.RollbackTo("sp1")

Complete Transaction Example

Bank Transfer Example

go
func TransferMoney(db *gorm.DB, fromID, toID uint, amount float64) error { tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() // Query source account var fromAccount Account if err := tx.Where("id = ?", fromID).First(&fromAccount).Error; err != nil { tx.Rollback() return err } // Check if balance is sufficient if fromAccount.Balance < amount { tx.Rollback() return errors.New("Insufficient balance") } // Deduct from source account if err := tx.Model(&fromAccount).Update("balance", fromAccount.Balance-amount).Error; err != nil { tx.Rollback() return err } // Query destination account var toAccount Account if err := tx.Where("id = ?", toID).First(&toAccount).Error; err != nil { tx.Rollback() return err } // Add to destination account if err := tx.Model(&toAccount).Update("balance", toAccount.Balance+amount).Error; err != nil { tx.Rollback() return err } // Record transaction log transaction := Transaction{ FromAccountID: fromID, ToAccountID: toID, Amount: amount, Status: "completed", } if err := tx.Create(&transaction).Error; err != nil { tx.Rollback() return err } // Commit transaction return tx.Commit().Error }

Transaction Callbacks

GORM provides transaction callback methods to simplify transaction handling:

Transaction() Method

go
err := db.Transaction(func(tx *gorm.DB) error { // Execute operations in transaction if err := tx.Create(&user).Error; err != nil { // Returning error automatically rolls back return err } if err := tx.Create(&profile).Error; err != nil { return err } // Returning nil automatically commits return nil }) if err != nil { // Transaction failed }

Nested Transactions

go
err := db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&user).Error; err != nil { return err } // Nested transaction return tx.Transaction(func(tx2 *gorm.DB) error { return tx2.Create(&profile).Error }) })

Transaction Options

Set Transaction Isolation Level

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })

Read-only Transaction

go
tx := db.Begin(&sql.TxOptions{ ReadOnly: true, })

Common Operations in Transactions

Create Record

go
tx.Create(&user)

Update Record

go
tx.Model(&user).Update("name", "John")

Delete Record

go
tx.Delete(&user)

Query Record

go
tx.First(&user, 1)

Raw SQL

go
tx.Exec("UPDATE users SET name = ?", "John")

Transaction Error Handling

Check Transaction Status

go
tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() // Execute operations if err := tx.Create(&user).Error; err != nil { tx.Rollback() log.Printf("Transaction failed: %v", err) return err } // Commit transaction if err := tx.Commit().Error; err != nil { log.Printf("Commit failed: %v", err) return err }

Transaction Timeout Handling

go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tx := db.BeginTx(ctx, nil) // Execute operations...

Nested Transactions and Savepoints

Create Savepoint

go
tx.SavePoint("sp1")

Rollback to Savepoint

go
tx.RollbackTo("sp1")

Release Savepoint

go
tx.Exec("RELEASE SAVEPOINT sp1")

Notes

  1. Transaction Scope: Transactions should be used in the smallest possible scope to reduce lock time
  2. Error Handling: Must properly handle errors in transactions, ensure rollback on failure
  3. Resource Release: Use defer to ensure transactions are properly handled when function exits
  4. Isolation Level: Choose appropriate transaction isolation level based on business requirements
  5. Deadlock Prevention: Avoid holding locks for long periods, access resources in fixed order
  6. Performance Considerations: Transactions add database overhead, avoid unnecessary transactions

Best Practices

  1. Use Transaction() Callback: Simplifies transaction handling code
  2. Keep Short: Transactions should be as short and fast as possible
  3. Error Handling: Always check and handle errors
  4. Logging: Log transaction start, commit, and rollback
  5. Test Coverage: Write comprehensive test cases for transaction logic
  6. Avoid Nesting: Try to avoid overly deep nested transactions

Transaction Isolation Levels

Read Uncommitted

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadUncommitted, })

Read Committed

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelReadCommitted, })

Repeatable Read

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelRepeatableRead, })

Serializable

go
tx := db.Begin(&sql.TxOptions{ Isolation: sql.LevelSerializable, })
标签:Gorm