GORM

repository·master·Indexed 43 days ago

https://github.com/go-gorm/gorm.io

A type-safe, efficient developer library for Go to interact with relational databases. It features a Generics-based API, a CLI tool for code generation, and support for advanced queries, batch inserts, eager loading of many-to-many associations, and custom lifecycle hooks.

Tokens
141.8K
Snippets
552
Records
588
Agent score
47%

What's inside GORM

  1. Overview of GORM Features

    master

    GORM is a full-featured ORM for Golang providing the following capabilities:

    • Associations: Has One, Has Many, Belongs To, Many To Many, Polymorphism, Single-table inheritance.
    • Lifecycle Hooks: Before/After Create, Save, Update, Delete, Find.
    • Querying: Eager loading with Preload and Joins, SQL Builder, SubQueries, and Named Arguments.
    • Data Management: Transactions (including Nested and Save Points), Batch Insert, FindInBatches, and Auto Migrations.
    • Advanced SQL: Upsert, Locking, Optimizer/Index/Comment Hints, and Composite Primary Keys.
    • Extensibility: Plugin API for Database Resolvers (read/write splitting) and Prometheus integration.
    • Developer Experience: Context support, Prepared Statement Mode, DryRun Mode, and a Logger.
  2. Major Features in GORM 2.0

    master

    GORM 2.0 introduces several significant improvements and new capabilities:

    • Core Capabilities: Context support, Batch Insert, Prepared Statement Mode, DryRun Mode, Join Preload, Find To Map, Create From Map, and FindInBatches.
    • Transactions: Support for Nested Transactions, SavePoints, and RollbackTo SavePoint.
    • Advanced SQL: SQL Builder, Named Arguments, Group Conditions, Upsert, Locking, Optimizer/Index/Comment Hints, SubQuery improvements, and CRUD with SQL Expr and Context Valuer.
    • Relationships: Full self-reference relationships, Join Table improvements, and Association Mode for batch data.
    • Field Management: Support for multiple fields to track create/update time (including UNIX milli/nano seconds) and Field Permissions (read-only, write-only, create-only, update-only, ignored).
    • Extensibility: A new plugin system (with official plugins for multiple databases, read/write splitting, and Prometheus), a new Hooks API, and a new Logger with context support.
    • Schema & Migration: A new Migrator supporting foreign keys, smarter AutoMigrate, constraints/checker support, and enhanced index support.
    • Naming & Types: Unified Naming strategy (tables, fields, join tables, foreign keys, etc.) and better customized data type support (e.g., JSON).
  3. Core GORM Features Overview

    master

    GORM is a full-featured ORM for Golang that includes the following capabilities:

    • Associations: Has One, Has Many, Belongs To, Many To Many, Polymorphism, and Single-table inheritance.
    • Hooks: Lifecycle events like Before/After Create/Save/Update/Delete/Find.
    • Querying: Eager loading with Preload and Joins, SQL Builder, SubQueries, and Upsert.
    • Transactions: Support for Nested Transactions, Save Points, and Rollbacks.
    • Database Management: Auto Migrations, Composite Primary Keys, Indexes, and Constraints.
    • Advanced Features: Context support, Prepared Statement Mode, DryRun Mode, Batch Inserts, and Locking.
    • Extensibility: Plugin API for Database Resolvers (read/write splitting) and Prometheus integration.
  4. Use the Sharding plugin to split large tables

    master

    The Sharding plugin uses a SQL parser to split large tables into smaller shards and automatically redirects queries to the appropriate shard. It is designed to be non-intrusive, lightning-fast (running as Go code without network-based middleware), and supports multiple databases like PostgreSQL and MySQL. It also includes integrated primary key generators such as Snowflake or PostgreSQL Sequences.

    import (
        "gorm.io/driver/postgres"
        "gorm.io/gorm"
        "gorm.io/sharding"
    )
    
    db, err := gorm.Open(postgres.New(postgres.Config{DSN: "postgres://localhost:5432/sharding-db?sslmode=disable"}))
    
    db.Use(sharding.Register(sharding.Config{
        ShardingKey:         "user_id",
        NumberOfShards:      64,
        PrimaryKeyGenerator: sharding.PKSnowflake,
    }, "orders", Notification{}, AuditLog{}))
  5. Use the Sharding plugin for high-performance database access

    master
    The Sharding plugin uses a SQL parser to split large tables into smaller shards and automatically redirects queries to the appropriate sharding tables. It is designed to be non-intrusive, lightning-fast (running as Go code without network-based middleware), and supports multiple databases like PostgreSQL and MySQL. It also includes integrated primary key generators such as Snowflake, PostgreSQL Sequence, or Custom generators.
  6. What is a Has One association

    master

    A has one association establishes a one-to-one relationship between two models. It indicates that an instance of a model (the owner) contains or possesses exactly one instance of another model (the owned entity). In this relationship, the owner model holds the reference, but the foreign key is typically stored in the owned entity's table.

    // User has one CreditCard, UserID is the foreign key
    type User struct {
      gorm.Model
      CreditCard CreditCard
    }
    
    type CreditCard struct {
      gorm.Model
      Number string
      UserID uint
    }
  7. What is the Migrator interface and how to use it?

    master

    The Migrator interface provides a unified, database-independent API for performing manual schema changes. GORM handles the underlying SQL differences between databases (e.g., how SQLite handles ALTER COLUMN vs MySQL).

    Key capabilities include:

    • Tables: Create, drop, rename, or check for existence.
    • Columns: Add, drop, alter, rename, or check for existence.
    • Views: Create or drop views using ViewOption.
    • Constraints: Create, drop, or check for existence of constraints (including foreign keys).
    • Indexes: Create, drop, rename, or check for existence of indexes.
    // Access the migrator via the DB instance
    migrator := db.Migrator()
    
    // Example: Check if a table exists
    if migrator.HasTable(&User{}) {
        // ...
    }
  8. Define and use default values for fields

    master

    You can define default values for struct fields using the gorm:"default:value" tag. These values are used by the database when the field is a Go zero-value (e.g., 0, '', false).

    Handling Zero-Values

    Because GORM treats zero-values as 'empty', it won't send them to the database, which triggers the default tag. If you actually want to save a zero-value (like 0 or false) instead of the default, use a pointer type (e.g., *int) or a sql.Null* type.

    Database Functions and Generated Columns

    • To use a database function as a default (e.g., uuid_generate_v3()), use the default tag with the function call.
    • If you want to skip defining a default value during GORM migrations for a field that has a database-level default or virtual/generated value, use default:(-).
    • For virtual/generated columns, you may need to disable create/update permissions using Field-Level Permission.

    SQLite Limitation

    SQLite does not support default values during batch inserts. If you attempt a batch insert where some records rely on a default tag, it will fail. A workaround is to manually assign the default value within a BeforeCreate hook.

    type User struct {
      ID        string `gorm:"default:uuid_generate_v3()"` // database function
      FirstName string
      LastName  string
      Age       uint8
      // Generated column: skip migration definition with default:(-)
      FullName  string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"` 
    }
    
    // Workaround for SQLite batch insert default values
    func (p *Pet) BeforeCreate(tx *gorm.DB) (err error) {
        if p.Name == "" {
            p.Name = "cat"
        }
        return nil
    }
  9. Build query helpers with clause.Expression

    master

    If you are building custom query helpers (like specialized JSON queries), you can implement the clause.Expression interface. This allows your struct to participate in GORM's SQL building process.

    type Expression interface {
        Build(builder Builder)
    }

    This pattern is used by advanced query builders to generate dialect-specific SQL (e.g., using JSON_EXTRACT in MySQL vs -> in PostgreSQL) when performing operations like HasKey or Equals on complex data types.

    // Example of how a query helper might be used
    db.Find(&user, datatypes.JSONQuery("attributes").HasKey("role"))
  10. Ensure Goroutine Safety with New Session Methods

    master

    To optimize performance and reduce GC allocations, GORM V2 shares the Statement object during method chaining. Reusing a *gorm.DB instance that has been modified by a method chain (like .Where()) in multiple goroutines is not safe. To safely reuse a *gorm.DB instance in goroutines, you must first call a 'New Session Method' such as WithContext or Session to create a fresh statement instance.

    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    
    // SAFE: db is a fresh instance
    for i := 0; i < 100; i++ {
      go db.Where(...).First(&user)
    }
    
    // NOT SAFE: tx shares the same Statement as other goroutines
    tx := db.Where("name = ?", "jinzhu")
    for i := 0; i < 100; i++ {
      go tx.Where(...).First(&user)
    }
    
    // SAFE: ctxDB is a new session created via WithContext
    ctxDB := db.WithContext(ctx)
    for i := 0; i < 100; i++ {
      go ctxDB.Where(...).First(&user)
    }
    
    // SAFE: Session creates a new instance
    tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{})
    for i := 0; i < 100; i++ {
      go tx.Where(...).First(&user)
    }
  11. Ensure Goroutine Safety with Method Chaining

    master

    To optimize performance and reduce GC allocations, GORM V2 shares the Statement object during method chains. This means reusing a *gorm.DB instance that has already had conditions applied is not thread-safe.

    Safe patterns:

    • Using a freshly initialized *gorm.DB.
    • Using a *gorm.DB returned by a "New Session Method" such as .WithContext(ctx) or .Session(&gorm.Session{}).

    Unsafe pattern:

    • Reusing a variable that holds a chain of conditions (e.g., tx := db.Where("name = ?", "jinzhu")) across multiple goroutines.
    // SAFE: New initialized DB
    for i := 0; i < 100; i++ {
      go db.Where(...).First(&user)
    }
    
    // UNSAFE: Reusing a Statement from a chain
    tx := db.Where("name = ?", "jinzhu")
    for i := 0; i < 100; i++ {
      go tx.Where(...).First(&user) // Race condition!
    }
    
    // SAFE: After a New Session Method
    ctxDB := db.WithContext(ctx)
    for i := 0; i < 100; i++ {
      go ctxDB.Where(...).First(&user)
    }
    
    // SAFE: Using Session
    tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{})
    for i := 0; i < 100; i++ {
      go tx.Where(...).First(&user)
    }
  12. Safe Query Reuse with Generics API

    master

    The Generics API inherently prevents SQL pollution. When you define a reusable query base using gorm.G[T](db), each subsequent operation is isolated because the context is passed directly to the operation methods and the generic interface design prevents condition pollution.

    ctx := context.Background()
    
    // Define a reusable query base
    genericDB := gorm.G[User](db).Where("name = ?", "jinzhu")
    
    // First query
    user1, err1 := genericDB.Where("age > ?", 10).First(ctx)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu' AND age > 10 LIMIT 1
    
    // Second query (No pollution)
    user2, err2 := genericDB.Where("age > ?", 20).First(ctx)
    // SQL: SELECT * FROM users WHERE name = 'jinzhu' AND age > 20 LIMIT 1
    ctx := context.Background()
    genericDB := gorm.G[User](db).Where("name = ?", "jinzhu")
    
    user1, err1 := genericDB.Where("age > ?", 10).First(ctx)
    user2, err2 := genericDB.Where("age > ?", 20).First(ctx)