or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

associations.mdclause.mddatabase-operations.mdhooks.mdindex.mdlogger.mdmigrations.mdquery-building.mdschema.mdtransactions.md
tile.json

transactions.mddocs/

Transactions

GORM provides comprehensive transaction support for ensuring data consistency across multiple database operations.

Transaction Methods

Automatic Transactions

Execute a function within a transaction. The transaction is automatically committed if no error is returned, or rolled back if an error is returned.

func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) error

Usage:

err := db.Transaction(func(tx *gorm.DB) error {
    // Perform database operations
    if err := tx.Create(&User{Name: "Alice"}).Error; err != nil {
        // Return error to rollback
        return err
    }

    if err := tx.Create(&Order{UserID: 1, Amount: 100}).Error; err != nil {
        // Return error to rollback
        return err
    }

    // Return nil to commit
    return nil
})

if err != nil {
    // Transaction failed
}

Manual Transactions

Manually control transaction lifecycle with Begin, Commit, and Rollback.

// Begin a transaction
func (db *DB) Begin(opts ...*sql.TxOptions) *DB

// Commit the transaction
func (db *DB) Commit() *DB

// Rollback the transaction
func (db *DB) Rollback() *DB

Usage:

// Begin transaction
tx := db.Begin()

// Defer rollback in case of panic
defer func() {
    if r := recover(); r != nil {
        tx.Rollback()
    }
}()

// Perform operations
if err := tx.Create(&User{Name: "Alice"}).Error; err != nil {
    tx.Rollback()
    return err
}

if err := tx.Create(&Order{UserID: 1, Amount: 100}).Error; err != nil {
    tx.Rollback()
    return err
}

// Commit transaction
if err := tx.Commit().Error; err != nil {
    return err
}

Transaction Options

Specify transaction isolation level and other options.

import "database/sql"

// With transaction options
err := db.Transaction(func(tx *gorm.DB) error {
    // Operations here
    return nil
}, &sql.TxOptions{
    Isolation: sql.LevelReadCommitted,
    ReadOnly:  false,
})

// Manual transaction with options
tx := db.Begin(&sql.TxOptions{
    Isolation: sql.LevelSerializable,
    ReadOnly:  true,
})
defer tx.Rollback()
// ... operations
tx.Commit()

Savepoints

Savepoints provide fine-grained transaction control, allowing you to rollback to a specific point within a transaction.

// Create a savepoint
func (db *DB) SavePoint(name string) *DB

// Rollback to a savepoint
func (db *DB) RollbackTo(name string) *DB

Usage:

tx := db.Begin()

// Create first record
tx.Create(&User{Name: "Alice"})

// Create savepoint
tx.SavePoint("sp1")

// Create second record
tx.Create(&User{Name: "Bob"})

// Rollback to savepoint (Bob creation is rolled back, Alice remains)
tx.RollbackTo("sp1")

// Create third record
tx.Create(&User{Name: "Carol"})

// Commit transaction (Alice and Carol are committed)
tx.Commit()

Nested Savepoints

tx := db.Begin()

tx.Create(&User{Name: "Alice"})
tx.SavePoint("sp1")

tx.Create(&Order{UserID: 1})
tx.SavePoint("sp2")

tx.Create(&Item{OrderID: 1})

// Rollback to sp2 (Item creation is rolled back)
tx.RollbackTo("sp2")

// Rollback to sp1 (Order creation is also rolled back)
tx.RollbackTo("sp1")

tx.Commit()  // Only Alice is committed

Nested Transactions

GORM supports nested transactions using savepoints. When you call Begin within a transaction, it creates a savepoint instead of a new transaction.

db.Transaction(func(tx *gorm.DB) error {
    // First level transaction
    tx.Create(&User{Name: "Alice"})

    // Nested transaction (uses savepoint)
    return tx.Transaction(func(tx2 *gorm.DB) error {
        tx2.Create(&Order{UserID: 1})

        // This nested transaction can be rolled back independently
        if someCondition {
            return errors.New("nested transaction failed")
        }

        return nil
    })
    // If nested transaction fails, only Order is rolled back
    // If we return error here, both User and Order are rolled back
})

Disable Nested Transactions

// Disable nested transactions globally
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    DisableNestedTransaction: true,
})

// Disable for specific session
db.Session(&gorm.Session{
    DisableNestedTransaction: true,
}).Transaction(func(tx *gorm.DB) error {
    // Nested Begin will return error
    return nil
})

Connection Pool

Use a specific connection from the pool for all operations in the callback.

func (db *DB) Connection(fc func(tx *DB) error) error

Usage:

// All operations use the same connection
err := db.Connection(func(tx *gorm.DB) error {
    // All these operations share the same connection
    tx.First(&user)
    tx.Create(&order)
    tx.Update(&user, "status", "active")
    return nil
})

Transaction Configuration

Skip Default Transaction

By default, GORM runs Create, Update, Delete operations within a transaction to ensure data consistency. You can disable this for better performance.

// Disable default transaction globally
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
    SkipDefaultTransaction: true,
})

// Disable for specific operation
db.Session(&gorm.Session{
    SkipDefaultTransaction: true,
}).Create(&user)

Transaction in Session

Create a session with transaction settings.

tx := db.Session(&gorm.Session{
    SkipDefaultTransaction: true,
    DisableNestedTransaction: true,
})

// Use tx for operations
tx.Create(&user)

Error Handling

Proper error handling in transactions.

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

    // Validate business logic
    if user.Age < 18 {
        return errors.New("user must be 18 or older")  // Rollback
    }

    // Create related record
    order := Order{UserID: user.ID}
    if err := tx.Create(&order).Error; err != nil {
        return err  // Rollback
    }

    // Commit
    return nil
})

if err != nil {
    log.Printf("Transaction failed: %v", err)
}

Transaction Hooks

Lifecycle hooks work within transactions.

type User struct {
    gorm.Model
    Name string
    Age  int
}

func (u *User) BeforeCreate(tx *gorm.DB) error {
    // This runs within the transaction
    if u.Age < 18 {
        return errors.New("user must be 18 or older")
    }
    return nil
}

func (u *User) AfterCreate(tx *gorm.DB) error {
    // This runs within the transaction
    // Can create related records using tx
    return tx.Create(&Notification{
        UserID:  u.ID,
        Message: "Welcome!",
    }).Error
}

// Usage
err := db.Transaction(func(tx *gorm.DB) error {
    // Hooks will execute within this transaction
    return tx.Create(&User{Name: "Alice", Age: 25}).Error
})

Distributed Transactions

For distributed transactions across multiple databases, you need to manage them manually.

// Begin transactions on multiple databases
tx1 := db1.Begin()
tx2 := db2.Begin()

defer func() {
    if r := recover(); r != nil {
        tx1.Rollback()
        tx2.Rollback()
    }
}()

// Perform operations on first database
if err := tx1.Create(&user).Error; err != nil {
    tx1.Rollback()
    tx2.Rollback()
    return err
}

// Perform operations on second database
if err := tx2.Create(&log).Error; err != nil {
    tx1.Rollback()
    tx2.Rollback()
    return err
}

// Commit both transactions
if err := tx1.Commit().Error; err != nil {
    tx1.Rollback()
    tx2.Rollback()
    return err
}

if err := tx2.Commit().Error; err != nil {
    // tx1 is already committed, need to handle compensation
    return err
}

Best Practices

Keep Transactions Short

// Good: Short transaction
db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user)
    tx.Create(&order)
    return nil
})

// Bad: Long transaction with external I/O
db.Transaction(func(tx *gorm.DB) error {
    tx.Create(&user)

    // Avoid external I/O in transactions
    sendEmail(user.Email)  // BAD
    callExternalAPI()      // BAD

    tx.Create(&order)
    return nil
})

Proper Error Handling

db.Transaction(func(tx *gorm.DB) error {
    // Always check errors
    if err := tx.Create(&user).Error; err != nil {
        return err
    }

    // Business logic errors should also rollback
    if !isValidUser(&user) {
        return errors.New("invalid user")
    }

    return nil
})

Use Defer for Cleanup

tx := db.Begin()
defer func() {
    if r := recover(); r != nil {
        tx.Rollback()
    }
}()

// ... operations

if err := tx.Commit().Error; err != nil {
    tx.Rollback()
    return err
}

Transaction Context

Pass context through transactions for cancellation and deadlines.

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
    // Operations will respect context cancellation
    tx.Create(&user)
    tx.Create(&order)
    return nil
})

if err == context.DeadlineExceeded {
    // Handle timeout
}

Transaction Interfaces

GORM provides interfaces for transaction operations.

// TxBeginner interface
type TxBeginner interface {
    BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}

// ConnPoolBeginner interface
type ConnPoolBeginner interface {
    BeginTx(ctx context.Context, opts *sql.TxOptions) (ConnPool, error)
}

// TxCommitter interface
type TxCommitter interface {
    Commit() error
    Rollback() error
}

// Tx interface
type Tx interface {
    TxCommitter
    StmtContext(ctx context.Context, stmt *sql.Stmt) *sql.Stmt
}

// SavePointerDialectorInterface for savepoint support
type SavePointerDialectorInterface interface {
    SavePoint(tx *DB, name string) error
    RollbackTo(tx *DB, name string) error
}

These interfaces allow you to implement custom transaction behavior or use GORM with custom connection pools.