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 aUser
Structure 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 useCreate
Method 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 structuresuser
The pointer toCreate
method. Specifically, there are several reasons for doing this:
-
Pointer pass can modify the original structure
GORM's
Create
The 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.ID
orCreatedAt
field), ensure that the structure reflects the latest data in the database.For example,
user
ofID
Fields 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,Create
The method can be updated directlyuser
In the structureID
Field. -
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 execution
Create
When , 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 := (&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...)
-
&model
is 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 records
model
In the structure pointed to by the pointer. If no record is found, it returns an error.
exist(&user, 1)
middle,&user
It is pointing touser
Pointer to the structure. The pointer is passed here because GORM needs to be modifieduser
The value of the structure (i.e. fill the query result).
- By passing the pointer of the structure, GORM can directly assign the query result to
user
In 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 to
method, 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.
-
Find
The 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 to
Find
The 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 intousers
In 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,
Find
All records in the database table will be returned. - If you pass the query criteria,
Find
The 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:
Find
It also supports pagination query. You can passLimit
andOffset
Method to implement paging query. For example, query the first 10 records:
(10).Find(&users)
- Sort: You can also pass
Order
Method to specify how the query results are sorted. For example, sort by age:
("age desc").Find(&users)
- Return the number of records:
Find
The 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;user, 1) // Find users with primary key 1 = "Alice Updated" // Modify fields = 30 (&amp;user) // Update records}
-
(&user)
Will checkuser
Whether there is a primary key value (assuming the primary key exists). If it exists, it will perform the update operation, which willuser
The modified fields in the structure are updated to the database. - If the primary key does not exist, it will
user
Insert into the database.
Notice:
-
Save
All 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 use
Updates
orUpdate
method.
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;user, 1) // Find users with primary key 1 (&amp;user).Updates(User{Name: "Bob Updated", Age: 35}) }
In this example:
-
(&user).Updates(User{Name: "Bob Updated", Age: 35})
Will only updateuser
In the structureName
andAge
Field. -
(&user)
Indicates that the update isuser
Database records corresponding to the structure. -
Updates
The 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:
-
Updates
The 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. -
Updates
The 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 is
simplified version of , suitable for scenarios where only a single field is updated.
Example
func main() { var user User (&user, 1) // Find users with primary key 1 (&user).Update("Age", 40) // Update only the Age field}
-
(&user).Update("Age", 40)
Will beAge
The field is updated to40
, other fields remain unchanged. -
Update
Methods are suitable for situations where you only need to update a single field.
Notice:
-
Update
The method only updates the specified fields and will not affect other fields. - If the value of the field is zero,
Update
This field will also be updated (noZero value is ignored
mechanism).
Batch update: and
GORM also providesUpdateColumn
andUpdateColumns
Method, mainly used for batch update fields. These methods andUpdate
The methods are similar, but they do not trigger the GORM hook (e.g.BeforeSave
、AfterSave
wait).
UpdateColumn Example
(&user).UpdateColumn("Age", 45)
UpdateColumn
The GORM will not be triggeredBeforeSave
andAfterSave
Hooks, therefore suitable for situations where these hooks need to be bypassed.
UpdateColumns Example
(&user).UpdateColumns(map[string]interface{}{"Age": 50, "Name": "Charlie Updated"})
UpdateColumns
The batch update will be performed based on the incoming fields. andUpdate
Differently, 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 passWhere
Method specifies the update conditions.Where
Methods can be used withUpdates
orUpdate
Use 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 criteriaName
Field.
Notice:
-
Where
can 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 all
age > 30
Users, put theirName
Modify the field to"Batch Update"
。
Notice:
-
Updates
All 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(&User{}).Where("age > ?", 30).Updates(User{Name: "Transactional Update"}) (&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 recordsDelete
method:
func deleteUser(db *) { var user User (&user, 1) // Find the user to be deleted // Delete the user result := (&user) if != nil { panic("Failed to delete user") } ("User deleted:", ) }
5. Relationship and association query
GORM supports relational mapping between tables. For example, we haveUser
andPost
The relationship between. A user can have multiple posts, and can usehas many
relation.
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 haveUser
andPost
Two tables, you can usePreload
To load the associatedPost
data.
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 useCreate
Method 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. - Returned
tx
It is a transaction object, and all database operations should be passedtx
to 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 objecttx
to execute, not directly throughdb
implement.
Example
tx := () // Start a transaction // Perform multiple database operationsif err := (&user).Error; err != nil { () // Operation failed, rollback transaction return err } if err := (&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 := (&user).Error; err != nil { () // An error occurs, rollback transaction return err } // Perform operation 2if err := (&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 := (&user).Error; err != nil { () return err } // Update order tableif err := (&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 := (&user).Error; err != nil { () return err } nestedTx := () // Start nested transactions // Nested transaction operationsif err := (&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 MODE
Locks 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!