SoFunction
Updated on 2025-04-11

GORM usage tutorial

GORM is an ORM (Object-Relational Mapping) library for the Go language. It maps tables of relational databases with structures in Go, allowing developers to operate databases in an object-oriented manner without having to write SQL statements directly. Through GORM, developers can use the structures and methods of the Go language to perform common database operations (such as query, insertion, update, deletion, etc.), greatly simplifying the interaction process with the database.

1. Install GORM

First, you need to install the GORM library. Open the terminal and run the following command:

go get -u /gorm
go get -u /driver/sqlite  # Sample database, which can be replaced with other database drivers according to requirements

2. Create an infrastructure

A core concept of ORM is a structure, which represents a mapping of database tables. For example, suppose you have a "user" table, we can create aUserStructure to represent it.

package main

import (
    "/driver/sqlite"
    "/gorm"
)

type User struct {
    ID        uint   `gorm:"primaryKey"`  // Primary key    Name      string `gorm:"size:100"`    // User name field, limit length is 100    Age       uint   // User age    CreatedAt   // Creation time (GORM will be automatically managed)}

func main() {
    // Create a database connection    db, err := ((""), &{})
    if err != nil {
        panic("failed to connect to the database")
    }

    // Automatic migration: Automatically create tables based on structures through GORM    (&User{})
}

In GORM, tags of the structure field are used to define and control how a Go structure is mapped to a column in a database table. GORM supports many tags, which can configure column properties, indexes, relationships, etc. of database tables. Here are the common GORM field tags and their functions:

Label describe Example
primaryKey Specify the field as the primary key. gorm:"primaryKey"
size Specifies the size of the field, usually used in string fields. gorm:"size:100"
unique Create a unique index to ensure that the field values ​​are unique. gorm:"unique"
not null The specified field cannot be empty. gorm:"not null"
default Specifies the default value of the field. gorm:"default:0"
autoIncrement Set the field to the auto-increment field. Typically used for primary keys of integer types. gorm:"autoIncrement"
index Create an index for the field. gorm:"index"
uniqueIndex Creates a unique index for the field. gorm:"uniqueIndex"
index:<name> Create an index with a custom name. gorm:"index:idx_name"
foreignKey Specifies a foreign key field for use in one-to-many or many-to-many relationships. gorm:"foreignKey:UserID"
references Specifies the fields referenced in the foreign key relationship. gorm:"references:ID"
embedded Embed a structure and flatten the embedded structure field into the parent structure. gorm:"embedded"
preload Preload associated data when querying. gorm:"preload"
createdAt Automatically generated create timestamp fields. gorm:"autoCreateTime"
updatedAt Automatically generated update timestamp field. gorm:"autoUpdateTime"
deletedAt Automatically generated delete timestamp field, supporting soft deletion. gorm:"softDelete"
softDelete Used to support soft delete function. Usually used with DeletedAt. gorm:"index"
column Specifies the column name in the database, used when the field name does not match the column name. gorm:"column:db_column_name"
type Specifies the type of the field in the database, usually used for special types (such as JSON). gorm:"type:text"
many2many Used for many-to-many relationships, specifying the name of the join table. gorm:"many2many:user_posts"

This table shows the commonly used field tags in GORM and how they affect the database table structure, helping developers better understand and use GORM for database operations.

3. Database connection and configuration

In the example above, we used()To connect to the SQLite database. If you use other databases such as MySQL or Postgres, you can replace the corresponding driver.

For example, the code connecting to a MySQL database is as follows:

db, err := (("user:password@tcp(localhost:3306)/dbname"), &{})

4. Database operations

4.1 Create a record

You can useCreateMethod to insert data. Here is how to insert a new user:

func createUser(db *) {
    user := User{Name: "John Doe", Age: 30}
    result := (&user)
    if  != nil {
        panic("Failed to create user")
    }
    ("User created:", )
}

In code(&user)use&Symbols are because we want to pass structuresuserThe pointer toCreatemethod. Specifically, there are several reasons for doing this:

  • Pointer pass can modify the original structure

    GORM'sCreateThe method accepts a pointer to the structure so that it can directly modify the value of the original structure, not just a copy. By passing pointers, GORM can modify the structure during the process of inserting into the database (for example, assigning values ​​to the fields of the structure, such as automatically generated by the database.IDorCreatedAtfield), ensure that the structure reflects the latest data in the database.

    For example,userofIDFields will be automatically assigned by GORM when inserted into the database (usually the primary key that increases automatically). If you are passing a pointer to the structure,CreateThe method can be updated directlyuserIn the structureIDField.

  • Avoid copying large structures

    If you are passing a copy of the structure, GORM will first create a copy of the structure and insert it into the database. This can waste memory and reduce performance for larger structures. Passing pointers avoids copying the entire structure, but only passes the memory address of the structure, which has higher performance.

  • How GORM works

    GORM uses pointers internally to identify changes in structure fields. By passing pointers, GORM can determine changes in the structure and perform corresponding processing. For example, in executionCreateWhen  , GORM will check the pointer of the structure to determine whether the field has been assigned, whether it needs to be automatically filled, etc.

4.2 Query records

GORM provides a variety of query methods, and data can be obtained through structure query, conditional query, etc.

Get a single record

func getUser(db *) {
    var user User
    result := (&amp;user, 1)  // Find records with primary key 1 in the user table and fill them into the user structure    if  != nil {
        panic("User not found")
    }
    ("User:", user)
}

is a query method provided by GORM, which is used to obtain the first record that meets the conditions from the database. It is usually used to query data based on primary keys or other conditions.

Basic syntax:

(&model, conditions...)
  • &modelis a pointer parameter that indicates that the result of the query will be filled into this structure.
  • conditions...is the query condition, which can be a primary key or other field.

If the query is successful,Will fill in the query recordsmodelIn the structure pointed to by the pointer. If no record is found, it returns an error.

exist(&user, 1)middle,&userIt is pointing touserPointer to the structure. The pointer is passed here because GORM needs to be modifieduserThe value of the structure (i.e. fill the query result).

  • By passing the pointer of the structure, GORM can directly assign the query result touserIn the structure.
  • If you are passing the structure itself, not the pointer, the query result will not be filled into the structure, because the structure will be passed as a copy tomethod, while GORM needs to be able to modify the field value of the original structure.

Get multiple records

func getUsers(db *) {
    var users []User
    result := (&users)
    if  != nil {
        panic("No users found")
    }
    ("Users:", users)
}

is one of the query methods provided by GORM, which is used to find multiple records and store them in the incoming slice structure.

  • FindThe method will look for records based on the passed conditions, which can be a simple query (such as all records) or a conditional query (such as filtering by field value).
  • Pass toFindThe parameter of  is a pointer that will fill the query record into the slice pointed to.

(&users)All records will be found from the database (or based on the incoming query conditions) and populated intousersIn slices. The result of the query will be a collection of structures. The Find method returns all records that meet the criteria by default.

  • If the query has no conditions,FindAll records in the database table will be returned.
  • If you pass the query criteria,FindThe results will be filtered according to the conditions.

Other functions of the Find method

  • Query Conditions: You can limit the result of a query by passing the query conditions. For example, if you want to find all users older than 30, you can write this:
(&users, "age > ?", 30)

This query returns all users older than 30 years old.

  • Pagination:FindIt also supports pagination query. You can passLimitandOffsetMethod to implement paging query. For example, query the first 10 records:
(10).Find(&users)
  • Sort: You can also passOrderMethod to specify how the query results are sorted. For example, sort by age:
("age desc").Find(&users)
  • Return the number of records:FindThe method will also return the query results, including the number of records found. If there is no record, it returns an empty slice.

4.3 Update records

In GORM, updating records is a common operation. You can update records through several methods provided by GORM. The following will introduce in detail the way to update records in GORM, including basic updates, partial updates, batch updates, etc., and explain the specific usage and precautions of each method.

Basic update: Method

Methods are used to save (or update) data in a structure. If the primary key of the structure already exists, GORM performs an update operation; if the primary key does not exist, GORM performs an insert operation (also known as "upsert"). therefore,It is not only suitable for updating existing records, but also for inserting new records.

Example

func main() {
    var user User
    (&amp;amp;user, 1) // Find users with primary key 1
     = "Alice Updated" // Modify fields     = 30

    (&amp;amp;user) // Update records}
  • (&user)Will checkuserWhether there is a primary key value (assuming the primary key exists). If it exists, it will perform the update operation, which willuserThe modified fields in the structure are updated to the database.
  • If the primary key does not exist, it willuserInsert into the database.

Notice:

  • SaveAll non-zero fields will be updated (that is, fields in the structure may not be updated if they are null), and all fields will be updated, even if you do not explicitly modify a certain field.
  • If you want to update only certain fields, you should useUpdatesorUpdatemethod.

Update some fields: Method

Methods allow you to update some fields in the structure, not all fields. It is a more precise method of updating, usually used to update only some modified fields in the structure.

Example

func main() {
    var user User
    (&amp;amp;user, 1) // Find users with primary key 1
    (&amp;amp;user).Updates(User{Name: "Bob Updated", Age: 35})
}

In this example:

  • (&user).Updates(User{Name: "Bob Updated", Age: 35})Will only updateuserIn the structureNameandAgeField.
  • (&user)Indicates that the update isuserDatabase records corresponding to the structure.
  • UpdatesThe parameters in the method can be a structure (such asUser{Name: "Bob Updated"}), it can also be amap[string]interface{}(The key is the field name, and the value is the value to be updated).

Notice:

  • UpdatesThe zero-value fields (such as empty strings, zero integers, etc.) will be ignored. If the value of a field is zero, it will not be updated.
  • (&user)Used to specify the model or table to be updated.
  • UpdatesThe modified fields will be updated, but the fields that are not specified in the model will not be updated.

Single field update: Method

If you only need to update a separate field, you can usemethod. This method is used to update a single field, and issimplified version of  , suitable for scenarios where only a single field is updated.

Example

func main() {
    var user User
    (&amp;user, 1) // Find users with primary key 1
    (&amp;user).Update("Age", 40) // Update only the Age field}
  • (&user).Update("Age", 40)Will beAgeThe field is updated to40, other fields remain unchanged.
  • UpdateMethods are suitable for situations where you only need to update a single field.

Notice:

  • UpdateThe method only updates the specified fields and will not affect other fields.
  • If the value of the field is zero,UpdateThis field will also be updated (noZero value is ignoredmechanism).

Batch update: and

GORM also providesUpdateColumnandUpdateColumnsMethod, mainly used for batch update fields. These methods andUpdateThe methods are similar, but they do not trigger the GORM hook (e.g.BeforeSaveAfterSavewait).

UpdateColumn Example

(&user).UpdateColumn("Age", 45)

UpdateColumnThe GORM will not be triggeredBeforeSaveandAfterSaveHooks, therefore suitable for situations where these hooks need to be bypassed.

UpdateColumns Example

(&user).UpdateColumns(map[string]interface{}{"Age": 50, "Name": "Charlie Updated"})

UpdateColumnsThe batch update will be performed based on the incoming fields. andUpdateDifferently, it won't trigger the model's hook.

Notice:

  • These two methods directly update the field and will not ignore the zero value of the field.
  • They only perform atomic updates of single fields and will not involve operations such as multi-table association.

Condition update: and

You can passWhereMethod specifies the update conditions.WhereMethods can be used withUpdatesorUpdateUse together for conditional updates.

Example

(&User{}).Where("age > ?", 30).Updates(User{Name: "Updated Name"})
  • In this example,Where("age > ?", 30)The update conditions are limited, and only users older than 30 will be updated.
  • Updates(User{Name: "Updated Name"})Update all users who meet the criteriaNameField.

Notice:

  • Wherecan help you construct complex update conditions, but can also be used separately as needed (for example, update certain records by ID).

Batch update (multiple records)

You can use()Methods and()Method to batch update multiple records. Here is an example of batch updates:

Example

(&User{}).Where("age > ?", 30).Updates(User{Name: "Batch Update"})
  • This example will update allage > 30Users, put theirNameModify the field to"Batch Update"

Notice:

  • UpdatesAll records that meet the criteria will be updated instead of just one record.

Update multiple records with transactions

If you need to ensure the atomicity of multiple update operations, you can put the update operation into one transaction. In GORM, transactions pass()start,()submit,()rollback.

Example

tx := ()

// Perform multiple update operations(&amp;User{}).Where("age &gt; ?", 30).Updates(User{Name: "Transactional Update"})
(&amp;User{}).Where("name = ?", "Bob").Update("Age", 40)

if err := ().Error; err != nil {
    ()
    ("Error:", err)
    return
}
  • ()Start a transaction.
  • ()Submit transactions,()Roll back the transaction in the event of an error to ensure the atomicity of all operations.

4.4 Delete records

You can use to delete recordsDeletemethod:

func deleteUser(db *) {
    var user User
    (&amp;user, 1)  // Find the user to be deleted
    // Delete the user    result := (&amp;user)
    if  != nil {
        panic("Failed to delete user")
    }
    ("User deleted:", )
}

5. Relationship and association query

GORM supports relational mapping between tables. For example, we haveUserandPostThe relationship between. A user can have multiple posts, and can usehas manyrelation.

5.1 Defining the associated structure

type Post struct {
    ID     uint
    Title  string
    Body   string
    UserID uint  // Foreign keys    User   User  // Associated User}

5.2 Associated Query

Assume we haveUserandPostTwo tables, you can usePreloadTo load the associatedPostdata.

func getUserWithPosts(db *) {
    var user User
    ("Posts").First(&user, 1)
    ("User:", )
    ("Posts:", )
}

5.3 Create an association record

When you insert a record with an associated one, you can useCreateMethod to insert both the master and slave data:

func createUserWithPosts(db *) {
    user := User{Name: "Alice", Age: 28, Posts: []Post{
        {Title: "Post 1", Body: "This is the first post"},
        {Title: "Post 2", Body: "This is the second post"},
    }}
    (&user)
    ("User and Posts created:", user)
}

6. Transactions

Transaction is a very important concept in GORM, especially when it is necessary to ensure that multiple database operations are either successful or all fail. Transactions can ensure the atomicity, consistency, isolation and durability of operations (i.e. ACID characteristics). If an operation in a transaction fails, the transaction can be rolled back, causing the database to return to its state before the transaction begins.

Transactions are a set of database operations that either execute all or do not execute all when an error occurs. Transactions provide atomicity, consistency, isolation and persistence in database operations (collectively known as ACID characteristics):

  • Atomicity: All operations in a transaction are either executed or none are executed. That is, transactions are an inseparable whole.
  • Consistency: Before and after the transaction is executed, the state of the database must be consistent and comply with the database integrity constraints.
  • Isolation: The execution of a transaction will not be interfered with by other transactions. Transaction execution is isolated from each other.
  • Durability: Once a transaction is committed, its changes to the database are permanent and will not be lost.

In GORM, you can use()To start a transaction, use()To submit the transaction, use()Roll back and forth transactions. The following are common usages of transactions in GORM.

6.1. Start a transaction: ()

You can pass()Start a transaction. This method returns a transaction object (*Type), through this object you can perform database operations.

tx := ()  // Start a transaction
  • ()A transaction will be created.
  • ReturnedtxIt is a transaction object, and all database operations should be passedtxto execute, not directlydb

6.2 Execute operations in transactions

In a transaction, you can perform a series of database operations. All operations should pass through the transaction objecttxto execute, not directly throughdbimplement.

Example

tx := ()  // Start a transaction
// Perform multiple database operationsif err := (&amp;user).Error; err != nil {
    ()  // Operation failed, rollback transaction    return err
}

if err := (&amp;user).Update("Age", 30).Error; err != nil {
    ()  // Operation failed, rollback transaction    return err
}
  • (&user)A record will be inserted into the transaction.
  • (&user).Update("Age", 30)The record will be updated in the transaction.

6.3 Submit transactions: ()

When all operations are successfully executed, you can call()Submit the transaction and save all changes to the database permanently.

if err := ().Error; err != nil {
    ()  // Submission failed, rollback transaction    return err
}
  • ()Transactions are committed, all operations are performed and persisted to the database.

6.4 Rollback transactions: ()

If an error is encountered during the transaction, it should be called()Roll back and forth transactions. In this way, all operations performed in the transaction will be revoked and the database will be restored to its state before the transaction begins.

if err := ().Error; err != nil {
    ("Error during rollback:", err)
    return err
}
  • ()All operations in the transaction will be revoked.

6.5 Using error handling in transactions

Usually, operations in transactions require error handling. As long as any operation fails, it should be called()Change back.

tx := ()

// Perform action 1if err := (&amp;user).Error; err != nil {
    ()  // An error occurs, rollback transaction    return err
}

// Perform operation 2if err := (&amp;user).Update("Age", 30).Error; err != nil {
    ()  // An error occurs, rollback transaction    return err
}

// All operations are successful, transaction is submittedif err := ().Error; err != nil {
    ()  // Submission failed, rollback transaction    return err
}

6.6 Multi-table operations in transactions

In a transaction, you can operate multiple tables, just use the same transaction object.tx, all table operations will be completed within one transaction.

Example: Multi-table operation

tx := ()

// Insert the user tableif err := (&amp;user).Error; err != nil {
    ()
    return err
}

// Update order tableif err := (&amp;order).Update("Status", "Shipped").Error; err != nil {
    ()
    return err
}

// Submit transactionif err := ().Error; err != nil {
    ()
    return err
}
  • A user is inserted here and the order status is updated, and all operations are performed in the same transaction.

6.7 Nesting of transactions

GORM does not directly support nested transactions (i.e., start another transaction in one transaction). However, you can manage transaction nesting manually. In nested transactions, only the outermost transaction decides whether to commit or rollback.

tx := ()

// External transaction operationsif err := (&amp;user).Error; err != nil {
    ()
    return err
}

nestedTx := ()  // Start nested transactions
// Nested transaction operationsif err := (&amp;order).Update("Status", "Shipped").Error; err != nil {
    ()  // Nested transaction rollback    ()        // External transaction rollback    return err
}

()  // Nested transaction commit
()  // External transaction submission
  • The above code demonstrates how to manually enable a nested transaction in a transaction. Committing and rollback of nested transactions affects the outermost transaction.

6.8 Concurrency issues in transactions

When using concurrent operations in transactions, you must be careful about data race and deadlock problems caused by concurrency. GORM default isolation level isReadCommitted, you can avoid some concurrency problems by configuring the transaction isolation level of the database.

tx := ().Set("gorm:query_option", "LOCK IN SHARE MODE")

// Transaction operation

at this time,LOCK IN SHARE MODELocks will be added during query to avoid other transactions from modifying the same row of data and preventing inconsistencies in data.

Summarize

GORM is a powerful and easy-to-use Go language ORM library that allows developers to interact with databases in an object-oriented way, reducing the complexity of writing and managing SQL statements. It is suitable for Go projects that need to handle databases, especially those involving large amounts of data operations, transaction support, and multi-table associations.

This is the end of this article about the tutorial on GO in GO. For more related GO GORM usage content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!