SoFunction
Updated on 2025-03-05

Golang uses gorm to add database exclusive locks, for update

Suitable for data competition scenarios that read first and then update, and locking operations should be placed in the transaction to prevent the lock from being automatically released. Reference for reasonsmysql doc

func UpdateUser(db *, id int64) error {
  tx := ()
  defer func() {
    if r := recover(); r != nil {
      ()
    }
  }()
  if err := ; err != nil {
    return err
  }
  user := User{}
  // Lock the User record with the specified id  if err := ("gorm:query_option", "FOR UPDATE").First(&user, id).Error; err != nil {
    ()
    return err
  }
  // Update operation...  // commit transaction, release the lock  if err := ().Error; err != nil {
    return err
  }
  return nil
}

Solution (lower efficiency):

var lock 
func UpdateUser(db *, id int64) error {
  ()
  // Database operation...  ()
  return nil
}

refer to

doc

Supplement: GORM model definition and database migration of Golang database programming

When developing applications, generally speaking, we first design data tables, and then use the development language to build the corresponding data model. However, today we are going to talk about a process of reverse operation, that is, how to define the data model of the GORM framework, and then use the defined data model to create the corresponding data table in the database by executing the application written by the GROM framework.

Therefore, we need to first talk about how to define the data model of GORM.

Model definition

Generally speaking, we say that the model definition of GROM refers to defining a struct representing a data table. Then we can use the GROM framework to map the struct into a data table of the corresponding relational database, or query the data in the data table to fill the struct. As shown below, we define a struct called Post.

type Post struct {
PostId int
Uid int
Title string
Content string
Type int
CreatedAt 
UpdatedAt 
}

Creating a good structure is only the first step, but don’t rush to create a data table. We need to understand the mapping rules between the structure and the data table. The main points are as follows:

Struct tags

We know that the structures of Go language support the use of tags to extend additional information for each field of the structure. For example, when using the standard library encoding/json package for JSON encoding, tags can be used to extend the encoding additional information.

The GROM framework has its own tags convention, as shown below:

Column Specify the column name

Type Specify the column data type

Size Specify the column size, default value 255

PRIMARY_KEY Specify the column as the primary key

UNIQUE Specify columns as unique

DEFAULT Specify the column default value

PRECISION Specify column accuracy

NOT NULL Specify a column as non-NULL

AUTO_INCREMENT Specifies whether the column is an auto-increment type

INDEX Creates an index with or without a name, and if multiple indexes have the same name, create a composite index

UNIQUE_INDEX is similar to INDEX, except that the unique index is created.

EMBEDDED Set the structure to embed

EMBEDDED_PREFIX Sets the prefix for the embedded structure

- Ignore this field

GROM also supports some tags agreements for associated data tables. I will talk about GROM data table associations when I have the opportunity.

The tags supported by GORM listed above are convenient for us to customize the mapping rules between structure fields and data table fields. In the following code, we customize some tags extensions for Post structures, as follows:

type Post struct {
PostId int `gorm:"primary_key;auto_increment"`
Uid int `gorm:"type:int;not null"`
Title string `gorm:"type:varchar(255);not null"`
Content string `gorm:"type:text;not null"`
Type uint8 `gorm:"type:tinyint;default 1;not null"`
CreatedAt 
UpdatedAt 
DeletedAt 
}

From the above example, we can see that GORM defines the format of tags for fields of the data model. Each field can be divided by multiple types of tags, and different tags are separated by semicolons.

Convention

In addition to the tags mentioned above that define the mapping rules between fields, when Go maps structures into relational data tables, it also has its own set of conventions, or conventions, which mainly include the following points:

Primary key

In the GROM convention, the ID field in the data model is generally mapped as the primary key of the data table. For example, the TestModel and ID are defined below are the primary key. The data type of the TestModel is string. If the data type of the ID is int, the GROM will also set AUTO_INCREMENT for this, and use the ID to become the auto-increment primary key.

type TestModel struct{
ID int
Name string
}

Of course, we can also customize the name of the primary key field, such as the Post structure above. We set the PostId field as the primary key. If we define other fields as the primary key, then even if there are still ID fields in the structure, the GROM framework will not regard the ID field as the primary key.

type Post struct {
ID int
PostId int `gorm:"primary_key;auto_increment"`
Uid int `gorm:"type:int;not null"`
Title string `gorm:"type:varchar(255);not null"`
Content string `gorm:"type:text;not null"`
Type uint8 `gorm:"type:tinyint;default 1;not null"`
CreatedAt 
UpdatedAt 
DeletedAt 
}

So, we add an ID field to the Post structure, and the PostId field is still the primary key. The following is the result printed using the desc posts statement in the data:

Data table mapping rules

When we use structures to create data tables, the name of the data table defaults to the lowercase plural form of the structure. For example, the data table name corresponding to the structure Post is posts. Of course, we can also specify the data table name corresponding to the structure ourselves, instead of using the default one.

Add TableName() method to the structure, and this method can return the customized data table name, as follows:

//Specify that the data table corresponding to the Post structure is my_postsfunc (p Post) TableName() string{
return "my_posts"
}

Data table prefix

In addition to specifying the data table name, we can also override this variable, so that we can specify a unified data table prefix for all data tables, as follows:

 = func (db *, defaultTableName string) string {
return "tb_" + defaultTableName;
}

In this way, the name of the data table created through the structure Post is tb_posts.

Field mapping rules

The structure to data table name mapping rules are plural of the structure name, while the default mapping rules for the structure field to data table fields are to separate words starting with underscores, as follows:

type Prize struct {
ID int
PrizeName string
}

The corresponding data table of the PrizeName field in the above structure Prize is price_name, but when we change PrizeName to Prizename, the corresponding data table field name is pricename, because only words starting with capital fields are separated.

Of course, we can also define tags extension information for a certain field of the structure, so that the mapping rules of the structure field to the data table field are defined in tags.

Time tracking

As we mentioned earlier, if there is a field with the name ID in the structure, the GORM framework will use this field as the primary key of the data table. In addition, if there are fields such as CreatedAt, UpdatedAt, and DeletedAt in the structure, the GROM framework will also do some special treatments, and the rules are as follows:

CreatedAt: This field will be automatically written when adding new data table records. UpdatedAt: This field will be automatically updated when updating the data table record. DeletedAt: When soft deletion is performed, this field will be automatically updated to indicate the deletion time

Since the GORM framework will automatically process these fields if there are more common fields in the structure, ID, CreatedAt, UpdatedAt, and DeletedAt. Therefore, if our structure needs these fields, we can directly embed the structure in the custom structure, and the structure is as follows:

type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt 
UpdatedAt 
DeletedAt * `sql:"index"`
}

So, if we embed it in the structure Prize, as follows:

type Prize struct{

Name string
}

In this case, the structure Prize contains five fields.

Database migration

The database migration we are talking about here means that by using a series of methods provided by GROM, we perform operations such as creating and deleting data tables based on the rules defined by the data model, that is, the DDL operation of the database.

GORM provides methods for performing DDL operations on databases, mainly the following categories:

Data table operation

//Create data tables automatically according to the modelfunc (s *DB) AutoMigrate(values ...interface{}) *DB 
//Create data tables based on the modelfunc (s *DB) CreateTable(models ...interface{}) *DB
//Delete the data table, which is equivalent to the drop table statementfunc (s *DB) DropTable(values ...interface{}) *DB 
//Equivalent to drop table if exsist statementfunc (s *DB) DropTableIfExists(values ...interface{}) *DB 
//Judge whether the data table exists based on the modelfunc (s *DB) HasTable(value interface{}) bool

Column operation

//Delete the data table fieldfunc (s *DB) DropColumn(column string) *DB
//Modify the data type of the data table fieldfunc (s *DB) ModifyColumn(column string, typ string) *DB

Indexing operations

//Add a foreign keyfunc (s *DB) AddForeignKey(field string, dest string, onDelete string, onUpdate string) *DB
//Add index to the data table fieldfunc (s *DB) AddIndex(indexName string, columns ...string) *DB
//Add a unique index to the data table fieldfunc (s *DB) AddUniqueIndex(indexName string, columns ...string) *DB

Data migration simple code example

Note that in the following example program, the db variable represents an object, and its initialization process will not be discussed in this article.

type User struct {
Id int //The self-increment id of the corresponding data tableUsername string
Password string
Email string
Phone string
}
func main(){
(&Post{},&User{})//Create posts and users data tables(&Post{})//Create posts data table("gorm:table_options", "ENGINE=InnoDB").CreateTable(&Post{})//When creating the posts table, it means that the engine exists(&Post{},"users")//Delete the posts and users table data table(&Post{},"users")//Before deletion, we will determine whether the posts and users tables exist.//First determine whether the users table exists, and then delete the users tableif ("users") {
("users")
}
//Delete the data table field(&Post{}).DropColumn("id")
//Modify field data type(&Post{}).ModifyColumn("id","varchar(255)")
//Create a foreign key relationship between posts and users table(&Post{}).AddForeignKey("uid", "users(id)", "RESTRICT", "RESTRICT")
//Add index to the title field of the posts table(&Post{}).AddIndex("index_title","title")
//Add a unique index to the phone field of the users table(&User{}).AddUniqueIndex("index_phone","phone")
}

summary

Maybe you will ask, isn’t it enough to just create, delete and other operations in the database? Why do these operations in the application? Because sometimes, we may not be able to log in to the database system, or we need to develop an application that can manage the database. At this time, the database migrations provided by the GROM framework can come in handy.