Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Connecting to a Database

To connect to the database, you first import the database driver. Such as:

import _ "github.com/go-sql-driver/mysql"
Copy the code

GORM already includes some drivers. To remember their import paths, you can import mysql drivers as follows

import _ "github.com/jinzhu/gorm/dialects/mysql"
// import _ "github.com/jinzhu/gorm/dialects/postgres"
// import _ "github.com/jinzhu/gorm/dialects/sqlite"
// import _ "github.com/jinzhu/gorm/dialects/mssql"
Copy the code
Supported databases

MySQL

Note: In order to properly handle time. time, you need to include parseTime as a parameter. (More supported parameters)

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
)

func main(a) {
  db, err := gorm.Open("mysql"."user:password@/dbname? charset=utf8&parseTime=True&loc=Local")
  defer db.Close()
}
Copy the code

PostgreSQL

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/postgres"
)

func main(a) {
  db, err := gorm.Open("postgres"."host=myhost port=myport user=gorm dbname=gorm password=mypassword")
  defer db.Close()
}
Copy the code

Sqlite3

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/sqlite"
)

func main(a) {
  db, err := gorm.Open("sqlite3"."/tmp/gorm.db")
  defer db.Close()
}
Copy the code

SQL Server

Get started with SQL Server, it can run on your Mac and Linux using Docker.

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mssql"
)

func main(a) {
  db, err := gorm.Open("mssql"."sqlserver://username:password@localhost:1433? database=dbname")
  defer db.Close()
}
Copy the code
CRUD interface
Create a record
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

db.NewRecord(user) // => Returns' true 'because the primary key is empty

db.Create(&user)

db.NewRecord(user) // => Create after 'user' returns' false '
Copy the code
The default value

You can define the default values for fields using tags, for example:

type Animal struct {
    ID   int64
    Name string `gorm:"default:'galeone'"`
    Age  int64
}
Copy the code

SQL then excludes fields with no values or zero values, and GORM loads the values of those fields from the database after the record is inserted.

var animal = Animal{Age: 99, Name: ""}
db.Create(&animal)
// INSERT INTO animals("age") values('99');
// SELECT name from animals WHERE ID=111; // The primary key is 111
// animal.Name => 'galeone'
Copy the code

Note that all fields with zero values, such as 0, “”, false, and other zero values are not saved to the database, but the default value for this field is used. You should consider using pointer types or other values to avoid this:

// Use pointer value
type User struct {
  gorm.Model
  Name string
  Age  *int `gorm:"default:18"`
}

// Use scanner/valuer
type User struct {
  gorm.Model
  Name string
  Age  sql.NullInt64 `gorm:"default:18"`
}
Copy the code
Set the field value in the hook

If you want to update the value of the field in the BeforeCreate function, you should use scope.setcolumn, for example:

func (user *User) BeforeCreate(scope *gorm.Scope) error {
  scope.SetColumn("ID", uuid.New())
  return nil
}
Copy the code
Create additional options
// Add additional options for inserting SQL statements
db.Set("gorm:insert_option"."ON CONFLICT").Create(&product)
// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;
Copy the code
The query
// Get the first record, sort by primary key
db.First(&user)
//// SELECT * FROM users ORDER BY id LIMIT 1;

// Get a record without specifying the sort
db.Take(&user)
//// SELECT * FROM users LIMIT 1;

// Get the last record, sort by primary key
db.Last(&user)
//// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// Get all the records
db.Find(&users)
//// SELECT * FROM users;

// Query by primary key (only if primary key is numeric)
db.First(&user, 10)
//// SELECT * FROM users WHERE id = 10;
Copy the code
Where

Native SQL

// Get the first matching record
db.Where("name = ?"."jinzhu").First(&user)
//// SELECT * FROM users WHERE name = 'jinzhu' limit 1;

// Get all matched records
db.Where("name = ?"."jinzhu").Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu';

// <>
db.Where("name <> ?"."jinzhu").Find(&users)

// IN
db.Where("name in (?) "And []string{"jinzhu"."jinzhu 2"}).Find(&users)

// LIKE
db.Where("name LIKE ?"."%jin%").Find(&users)

// AND
db.Where("name = ? AND age >= ?"."jinzhu"."22").Find(&users)

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
Copy the code
Struct & Map
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;

// Map
db.Where(map[string]interface{} {"name": "jinzhu"."age": 20}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// Multi-key slice query
db.Where([]int64{20.21.22}).Find(&users)
//// SELECT * FROM users WHERE id IN (20, 21, 22);
Copy the code

NOTE when querying from struct, GORM will query for non-zero values of these fields, meaning that your fields containing 0, “”, false, or other zero values will not appear in the query, such as:

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu";
Copy the code

Consider using pointer types or scanner/valuer to avoid this.

// Use pointer types
type User struct {
  gorm.Model
  Name string
  Age  *int
}

/ / using scanner/valuer
type User struct {
  gorm.Model
  Name string
  Age  sql.NullInt64
}
Copy the code
Not

This is similar to Where queries

db.Not("name"."jinzhu").First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;

/ / does not contain
db.Not("name"And []string{"jinzhu"."jinzhu 2"}).Find(&users)
//// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");

// Not in primary key slice
db.Not([]int64{1.2.3}).First(&user)
//// SELECT * FROM users WHERE id NOT IN (1,2,3);

db.Not([]int64{}).First(&user)
//// SELECT * FROM users;

/ / the native SQL
db.Not("name = ?"."jinzhu").First(&user)
//// SELECT * FROM users WHERE NOT(name = "jinzhu");

// Struct
db.Not(User{Name: "jinzhu"}).First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu";
Copy the code
Or
db.Where("role = ?"."admin").Or("role = ?"."super_admin").Find(&users)
//// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2"}).Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2';

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{} {"name": "jinzhu 2"}).Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2';
Copy the code
Inline condition query

This is similar to Where queries.

Note that when inline conditional queries are passed in using chained calls, they are not passed to subsequent intermediate methods.

// Query by primary key (only if primary key is numeric)
db.First(&user, 23)
//// SELECT * FROM users WHERE id = 23 LIMIT 1;
// Primary key query of non-numeric type
db.First(&user, "id = ?"."string_primary_key")
//// SELECT * FROM users WHERE id = 'string_primary_key' LIMIT 1;

/ / the native SQL
db.Find(&user, "name = ?"."jinzhu")
//// SELECT * FROM users WHERE name = "jinzhu";

db.Find(&users, "name <> ? AND age > ?"."jinzhu".20)
//// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

// Struct
db.Find(&users, User{Age: 20})
//// SELECT * FROM users WHERE age = 20;

// Map
db.Find(&users, map[string]interface{} {"age": 20})
//// SELECT * FROM users WHERE age = 20;
Copy the code
Additional query options
// Add additional options for query SQL
db.Set("gorm:query_option"."FOR UPDATE").First(&user, 10)
//// SELECT * FROM users WHERE id = 10 FOR UPDATE;
Copy the code
FirstOrInit

Gets the first matching record, or initializes a new record with a given condition (struct and map conditions only).

// Not found
db.FirstOrInit(&user, User{Name: "non_existing"})
//// user -> User{Name: "non_existing"}

/ / query
db.Where(User{Name: "Jinzhu"}).FirstOrInit(&user)
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
db.FirstOrInit(&user, map[string]interface{} {"name": "jinzhu"})
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
Copy the code
Attrs

If no record is found, the struct is initialized with parameters

// Not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
//// SELECT * FROM USERS WHERE name = 'non_existing';
//// user -> User{Name: "non_existing", Age: 20}

db.Where(User{Name: "non_existing"}).Attrs("age".20).FirstOrInit(&user)
//// SELECT * FROM USERS WHERE name = 'non_existing';
//// user -> User{Name: "non_existing", Age: 20}

/ / query
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user)
//// SELECT * FROM USERS WHERE name = jinzhu';
//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
Copy the code
Assign

Assign parameters to the struct whether or not data is queried

// Not found
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
//// user -> User{Name: "non_existing", Age: 20}

/ / query
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 30}).FirstOrInit(&user)
//// SELECT * FROM USERS WHERE name = jinzhu';
//// user -> User{Id: 111, Name: "Jinzhu", Age: 30}
Copy the code
FirstOrCreate

Gets the first matching record, or creates a record with the given condition (only for struct and map conditions).

// Not found
db.FirstOrCreate(&user, User{Name: "non_existing"})
//// INSERT INTO "users" (name) VALUES ("non_existing");
//// user -> User{Id: 112, Name: "non_existing"}

/ / query
db.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user)
//// user -> User{Id: 111, Name: "Jinzhu"}
Copy the code
Attrs

If no record is found, assign values to the struct with the given parameters, and then add a record using those values.

// Not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE name = 'non_existing';
//// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
//// user -> User{Id: 112, Name: "non_existing", Age: 20}

/ / query
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE name = 'jinzhu';
//// user -> User{Id: 111, Name: "jinzhu", Age: 20}
Copy the code
Assign

Whether it is queried or not, it is assigned to the record and saved to the database.

// Not found
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE name = 'non_existing';
//// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
//// user -> User{Id: 112, Name: "non_existing", Age: 20}

/ / query
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 30}).FirstOrCreate(&user)
//// SELECT * FROM users WHERE name = 'jinzhu';
//// UPDATE users SET age=30 WHERE id = 111;
//// user -> User{Id: 111, Name: "jinzhu", Age: 30}
Copy the code
Advanced query
The subquery

Use *gorm.expr for subqueries

db.Where("amount > ?", DB.Table("orders").Select("AVG(amount)").Where("state = ?"."paid").QueryExpr()).Find(&orders)
// SELECT * FROM "orders" WHERE "orders"."deleted_at" IS NULL AND (amount > (SELECT AVG(amount) FROM "orders" WHERE (state = 'paid')));
Copy the code
The query

Specify the fields to retrieve from the database; by default, all fields are selected.

db.Select("name, age").Find(&users)
//// SELECT name, age FROM users;

db.Select([]string{"name"."age"}).Find(&users)
//// SELECT name, age FROM users;

db.Table("users").Select("COALESCE(age,?) ".42).Rows()
//// SELECT COALESCE(age,'42') FROM users;
Copy the code
Order

When records are queried from the database using Order, the previously defined condition is overridden when the second parameter is set to true.

db.Order("age desc, name").Find(&users)
//// SELECT * FROM users ORDER BY age desc, name;

// Multiple sort criteria
db.Order("age desc").Order("name").Find(&users)
//// SELECT * FROM users ORDER BY age desc, name;

// Reorder
db.Order("age desc").Find(&users1).Order("age".true).Find(&users2)
//// SELECT * FROM users ORDER BY age desc; (users1)
//// SELECT * FROM users ORDER BY age; (users2)
Copy the code
Limit

Specifies the maximum number of records to query

db.Limit(3).Find(&users)
//// SELECT * FROM users LIMIT 3;

// Remove the LIMIT condition with -1
db.Limit(10).Find(&users1).Limit(- 1).Find(&users2)
//// SELECT * FROM users LIMIT 10; (users1)
//// SELECT * FROM users; (users2)
Copy the code
Offset

Specifies the number of records to skip before starting to return records.

db.Offset(3).Find(&users)
//// SELECT * FROM users OFFSET 3;

// Cancel OFFSET with -1
db.Offset(10).Find(&users1).Offset(- 1).Find(&users2)
//// SELECT * FROM users OFFSET 10; (users1)
//// SELECT * FROM users; (users2)
Copy the code
Count

Gets the number of model records

db.Where("name = ?"."jinzhu").Or("name = ?"."jinzhu 2").Find(&users).Count(&count)
//// SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)
//// SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count)

db.Model(&User{}).Where("name = ?"."jinzhu").Count(&count)
//// SELECT count(*) FROM users WHERE name = 'jinzhu'; (count)

db.Table("deleted_users").Count(&count)
//// SELECT count(*) FROM deleted_users;
Copy the code

Note: When using Count in the query chain, it must be in the last place because it overrides the SELECT query condition.

Group and Having
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
    ...
}

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?".100).Rows()
for rows.Next() {
    ...
}

type Result struct {
    Date  time.Time
    Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?".100).Scan(&results)
Copy the code
Joins

Specify association conditions

rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
    ...
}

db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

// Multiple associated queries
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?"."[email protected]").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?"."411111111111").Find(&user)
Copy the code
Pluck

Use Pluck to query a single column from the model as a collection. If you want to query multiple columns, you should use Scan instead.

var ages []int64
db.Find(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

// Requesting more than one column? Do it like this:
db.Select("name, age").Find(&users)
Copy the code
Scan

Put the Scan query results into another structure.

type Result struct {
    Name string
    Age  int
}

var result Result
db.Table("users").Select("name, age").Where("name = ?".3).Scan(&result)

// Raw SQL
db.Raw("SELECT name, age FROM users WHERE name = ?".3).Scan(&result)
Copy the code
update
Update all fields

The Save method will include all fields when performing SQL update operations, even if they have not been modified.

db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)

//// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
Copy the code
Update the changed fields

If you only want to Update fields that have already been modified, use the Update, Updates method.

// If a single attribute is changed, update it
db.Model(&user).Update("name"."hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// Update individual attributes with combined conditions
db.Model(&user).Where("active = ?".true).Update("name"."hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

// Using 'map' to update multiple attributes will update only those fields that have been changed
db.Model(&user).Updates(map[string]interface{} {"name": "hello"."age": 18."actived": false})
//// UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

// Using 'struct' to update multiple attributes, only the modified and non-empty fields will be updated
db.Model(&user).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// Warning: When using struct update, GORM will only update fields that are not empty
// For example in the following update, nothing will be updated because fields like "", 0, false are null values for these field types
db.Model(&user).Updates(User{Name: "", Age: 0, Actived: false})
Copy the code
Updates the selected field

If you only want to update or Omit certain fields, use the Select and Omit methods.

db.Model(&user).Select("name").Updates(map[string]interface{} {"name": "hello"."age": 18."actived": false})
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

db.Model(&user).Omit("name").Updates(map[string]interface{} {"name": "hello"."age": 18."actived": false})
//// UPDATE users SET age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
Copy the code
Update the column hook method

The update operations above perform the BeforeUpdate and AfterUpdate methods of the model to update the UpdatedAt timestamp and save its association. If you don’t want to do that, you can use UpdateColumn, UpdateColumns.

// Update single attribute, similar with `Update`
db.Model(&user).UpdateColumn("name"."hello")
//// UPDATE users SET name='hello' WHERE id = 111;

// Update multiple attributes, similar with `Updates`
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18 WHERE id = 111;
Copy the code
Batch update

The hook function does not execute during batch updates

db.Table("users").Where("id IN (?) "And []int{10.11}).Updates(map[string]interface{} {"name": "hello"."age": 18})
//// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

// Using struct updates will only apply to non-zero values, or map[string]interface{}
db.Model(User{}).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18;

// Use 'RowsAffected' to get the number of records affected by the update
db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected
Copy the code
SQL updates with expressions
DB.Model(&product).Update("price", gorm.Expr("price * ? +?".2.100))
//// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';

DB.Model(&product).Updates(map[string]interface{} {"price": gorm.Expr("price * ? +?".2.100)})
//// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';

DB.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?".1))
//// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2';

DB.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?".1))
//// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2' AND quantity > 1;
Copy the code
Update the value in the hook function

If you want to modify the updated value using the BeforeUpdate, BeforeSave hook functions, you can use the scope.SetColumn method, for example:

func (user *User) BeforeSave(scope *gorm.Scope) (err error) {
  if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
    scope.SetColumn("EncryptedPassword", pw)
  }
}
Copy the code
Additional update options
// Add additional SQL options in the update SQL statement
db.Model(&user).Set("gorm:update_option"."OPTION (OPTIMIZE FOR UNKNOWN)").Update("name"."hello")
//// UPDATE users SET name='hello', updated_at = '2013-11-17 21:34:10' WHERE id=111 OPTION (OPTIMIZE FOR UNKNOWN);
Copy the code
delete
Delete records

Warning: When deleting a record, you need to make sure that the primary key of the record has a value. GORM uses the primary key to delete the record. If the primary key field is empty, GORM deletes all records in the model.

// Delete an existing record
db.Delete(&email)
//// DELETE from emails where id=10;

// Add additional options for deleting SQL statements
db.Set("gorm:delete_option"."OPTION (OPTIMIZE FOR UNKNOWN)").Delete(&email)
//// DELETE from emails where id=10 OPTION (OPTIMIZE FOR UNKNOWN);
Copy the code
Batch delete

Delete all matched records

db.Where("email LIKE ?"."%jinzhu%").Delete(Email{})
//// DELETE from emails where email LIKE "%jinzhu%";

db.Delete(Email{}, "email LIKE ?"."%jinzhu%")
//// DELETE from emails where email LIKE "%jinzhu%";
Copy the code
Soft delete

If the model has a DeletedAt field, it automatically has soft delete capability! When a delete operation is performed, data is not permanently deleted from the database, but the value of DeletedAt is updated to the current time.

db.Delete(&user)
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

// Batch delete
db.Where("age = ?".20).Delete(&User{})
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

// Soft delete records are ignored when querying records
db.Where("age = 20").Find(&user)
//// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

// Use the Unscoped method to find soft delete records
db.Unscoped().Where("age = 20").Find(&users)
//// SELECT * FROM users WHERE age = 20;

// Delete the record permanently using the Unscoped method
db.Unscoped().Delete(&order)
//// DELETE FROM orders WHERE id=10;
Copy the code
The transaction

By default, GORM performs a single CREATE, UPDATE, and DELETE operation in a transaction to ensure database data integrity.

If you want to treat multiple CREATE, UPDATE, and DELETE operations as one atomic operation, Transaction is created for this purpose.

To perform a set of operations in a transaction, the normal flow is as follows.

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

// Perform some database operations in a transaction (use 'tx' from here instead of 'db')
tx.Create(...)

// ...

// An error occurred to rollback the transaction
tx.Rollback()

// Or commit the transaction
tx.Commit()
Copy the code
Concrete example
func CreateAnimals(db *gorm.DB) err {
  // Note that tx is used as the database handle in the transaction
  tx := db.Begin()
  defer func(a) {
    if r := recover(a); r ! =nil {
      tx.Rollback()
    }
  }()

  iftx.Error ! =nil {
    return err
  }

  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err ! =nil {
     tx.Rollback()
     return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err ! =nil {
     tx.Rollback()
     return err
  }

  return tx.Commit().Error
}
Copy the code