Contents

go第三方库-github.com.go-gorm.gorm

godoc

官方API

官方文档

李文周博客

模型定义

按照约定定义struct

约定

ID 作为主键

结构体名+s 表名

字段名+s 列名

CreatedAt 创建时间

UpdatedAt 更新时间

gorm.Model内嵌以复用开发者提供的代码

1
2
3
4
5
6
7
// gorm.Model 的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

字段标签

连接到数据库

获取数据库连接

1
2
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

现有database/sql连接初始化一个新的*gorm.DB

1
2
3
gormDB, err := gorm.Open(mysql.New(mysql.Config{
  Conn: sqlDB,
}), &gorm.Config{})

连接池

gorm使用database/sql标准库维护连接池

func (db *gorm.DB) DB() (*sql.DB, error)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sqlDB, err := db.DB()

// SetMaxIdleConns 设置空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)

// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)

// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)

CRUD接口

创建

完整结构体创建记录

1
result := db.Create(&user) // 通过数据的指针来创建

用指定的字段创建记录:

1
db.Select("Name", "Age", "CreatedAt").Create(&user)

忽略字段创建记录

1
db.Omit("Name", "Age", "CreatedAt").Create(&user)

批量插入(大量数据时一定要批量插入):

  1. 将一个 slice 传递给 Create 方法(也是传递引用),这将仅生成一句sql来创建
  2. CreateInBatches 分批创建,每一批生成一局sql可以设置批次大小db.CreateInBatches(&users, 100)

创建钩子:Model的BeforeSave, BeforeCreate, AfterSave, AfterCreate方法

1
func (u *User) BeforeCreate(tx *gorm.DB) (err error)

可以返回错误取消创建

SkipHooks 会话模式跳过钩子函数

1
DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)

根据 Map 创建:

1
2
3
4
5
6
7
8
9
db.Model(&User{}).Create(map[string]interface{}{
  "Name": "jinzhu", "Age": 18,
})

// batch insert from `[]map[string]interface{}{}`
db.Model(&User{}).Create([]map[string]interface{}{
  {"Name": "jinzhu_1", "Age": 18},
  {"Name": "jinzhu_2", "Age": 20},
})

关联创建:创建关联数据时,如果关联值是非零值,这些关联会被 upsert,且它们的 Hook 方法也会被调用

默认值:标签 default 为字段定义默认值

1
2
3
4
5
type User struct {
  ID   int64
  Name string `gorm:"default:galeone"`
  Age  int64  `gorm:"default:18"`
}

查询

Gorm提供了很多好用的方法,比如查询表中第一个记录等等,但是我认为这些方法都是冗余的没必要去记住(增加学习难度),使用元方法(不可替代),比如Scan()方法和Table()方法一起用可以替代First(),Last(),Take()和Find(),查询结果直接读到第一个参数中,如果是多条记录则使用一个slice来接收。

上面这些方法还可以将结果给到一个map[string]interface{}

其实Scan()方法都可以使用Rows()和代替,因为gorm是基于官方database.sql库的。

sql.Row对象的读取方法见Row&Rows

另外这些方法都是可以链式调用的(这些方法都返回一个会话对象,属于同一个会话,并且之前方法添加的条件,在后面方法中仍然生效)

有两种将查询结果映射到结构体的方法

  1. 加tag
  2. sql里面使用别名,别名和结构体对应(下划线和驼峰)

非常复杂的查询可以直接用Raw方法写原生sql

Find

func (db *DB) Find(dest interface{}, conds …interface{}) (tx *DB):将查询结果给到dst对象(为一个slice或者实例),相当于Scan和Table

1
2
3
4
5
6
// Get all records
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error

Where

func (db *DB) Where(query interface{}, args …interface{}) (tx *DB):添加任意查询条件

1
2
3
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

where可以直接用time.Time做参数

Not

func (db *DB) Not(query interface{}, args …interface{}) (tx *DB):添加非条件

1
2
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;

Or

func (db *DB) Or(query interface{}, args …interface{}) (tx *DB):添加或条件

1
2
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

Select

func (db *DB) Select(query interface{}, args …interface{}) (tx *DB):选择特定字段(与之对应的还有一个Omit,忽略字段)

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

Table与Model

From子句

func (db *DB) Table(name string, args …interface{}) (tx *DB):直接给出表名

func (db *DB) Model(value interface{}) (tx *DB):根据对象类型或切片对应类型推测表名(注意可以使用切片)

Order

func (db *DB) Order(value interface{}) (tx *DB):添加order by条件

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

Limit

func (db *DB) Limit(limit int) (tx *DB):添加limit条件,负数取消该会话之前添加的limit条件

1
2
3
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)

Group

func (db *DB) Group(name string) (tx *DB):添加grop by条件,通常和Table()方法一起用,Rows()方法返回一个*sql.Rows对象需要关闭,防止再次被枚举

1
2
3
4
5
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
defer rows.Close()
for rows.Next() {
  ...
}

Distinct

func (db *DB) Distinct(args …interface{}) (tx *DB):添加distinct条件

1
db.Distinct("name", "age").Order("name, age desc").Find(&results)

Joins

func (db *DB) Joins(query string, args …interface{}) (tx *DB):添加Join条件,当该方法先于select()方法调用时为Join预加载,即将全表先进行连接再进行查询

1
2
db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id

Scan

func (db *DB) Scan(dest interface{}) (tx *DB):与Find()方法一样但是不根据结构体读取表名

1
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATE

db.Clauses(clause.Locking{
  Strength: "SHARE",
  Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`

db.Clauses(clause.Locking{
  Strength: "UPDATE",
  Options: "NOWAIT",
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT

子查询

子查询:*gorm.DB 对象作为(?)参数,再次证明gorm是一个sql字符串拼接器

1
2
3
4
5
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

命名参数

sql.NamedArg 和 map[string]interface{}{}

1
2
3
4
5
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu"}).First(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1

优化器、索引提示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import "gorm.io/hints"

//优化器
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`

//索引提示
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)

db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})
// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"

迭代

func (db *DB) ScanRows(rows *sql.Rows, dest interface{}) error

FindInBatches

不直接获取全量数据,批量获取数据,与sql.Rows类似,推荐使用,但是需要定义与表结构相同的结构体来获取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 每次批量处理 100 条
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
  for _, result := range results {
    // 批量处理找到的记录
  }

  tx.Save(&results)

  tx.RowsAffected // 本次批量操作影响的记录数

  batch // Batch 1, 2, 3

  // 如果返回错误会终止后续批量操作
  return nil
})

result.Error // returned error
result.RowsAffected // 整个批量操作影响的记录数

查询钩子

AfterFind

1
2
3
4
5
6
func (u *User) AfterFind(tx *gorm.DB) (err error) {
  if u.Role == "" {
    u.Role = "user"
  }
  return
}

Scope

像调用自己的方法一样地调用func xxx(db *gorm.DB) *gorm.DB类型函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
  return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "C")
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查找所有金额大于 1000 的信用卡订单

Count

获取匹配的记录数

1
2
3
var count int64
db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'

更新

更新单个列

1
2
3
// 条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

更新多列

通过Table或者Model获取表,然后调用Update()传入一个表记录对象或者一个map[string]interface{}的对象,将满足条件的记录修改为对象的非nil值,可以配合Select一起使用更新选定字段会,select会更新nil值(所以推荐使用map更新):

1
2
3
4
// 使用 Map 进行 Select
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

更新 Hook

BeforeSave, BeforeUpdate, AfterSave, AfterUpdate

1
2
3
4
5
6
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to update")
    }
    return
}

批量更新

添加查询条件更新

阻止全局更新

全局更新需要添加选择条件或者使用原生Sql或者设置session AllowGlobalUpdate为真,否则返回ErrMissingWhereClause错误

1
2
3
4
5
6
7
8
db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu" WHERE 1=1

db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu"

更新的记录数

更新的记录数使用*gorm的属性查看

1
2
3
4
5
6
// 通过 `RowsAffected` 得到更新的记录数
result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';

result.RowsAffected // 更新的记录数
result.Error        // 更新的错误

使用 SQL 表达式更新

func Expr(expr string, args …interface{}) clause.Expr :生成sql表达式,作为更新值或者替换?占位符

1
2
3
// product 的 ID 是 `3`
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" = 3;

根据子查询进行更新

子查询作为更新值参数,通常子查询的结果是一个值而非多个记录或者多个字段

1
2
db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
// UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);

不使用 Hook 和时间追踪

UpdateColumn、UpdateColumns不追踪更新时间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 更新单个列
db.Model(&user).UpdateColumn("name", "hello")
// UPDATE users SET name='hello' WHERE id = 111;

// 更新多个列
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE id = 111;

// 更新选中的列
db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
// UPDATE users SET name='hello', age=0 WHERE id = 111;

返回修改行的数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 返回所有列
var users []User
DB.Model(&users).Clauses(clause.Returning{}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
// UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING *
// users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}

// 返回指定的列
DB.Model(&users).Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
// UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING `name`, `salary`
// users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}

检查字段是否有变更

Before Update Hook调用Changed

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
  // 如果 Role 字段有变更
    if tx.Statement.Changed("Role") {
    return errors.New("role not allowed to change")
    }

  if tx.Statement.Changed("Name", "Admin") { // 如果 Name 或 Role 字段有变更
    tx.Statement.SetColumn("Age", 18)
  }

  // 如果任意字段有变更
    if tx.Statement.Changed() {
        tx.Statement.SetColumn("RefreshedAt", time.Now())
    }
    return nil
}

在 Update 时修改值

Before 钩子中,使用Save变更整个对象,或者使用SetColumn更新一个字段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func (user *User) BeforeSave(tx *gorm.DB) (err error) {
  if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
    tx.Statement.SetColumn("EncryptedPassword", pw)
  }

  if tx.Statement.Changed("Code") {
    user.Age += 20
    tx.Statement.SetColumn("Age", user.Age)
  }
}

db.Model(&user).Update("Name", "jinzhu")

删除

批量删除

添加条件然后调用Delete(dst)方法,dst为目标表记录对象或对象切片的地址

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

Delete Hook

BeforeDelete、AfterDelete

1
2
3
4
5
6
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to delete")
    }
    return
}

阻止全局删除

加永真条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式

返回删除行的数据

Clauses(clause.Returning{})

Clauses(clause.Returning{Columns: []clause.Column{{Name: “name”}, {Name: “salary”}}})

软删除

模型包含了gorm.deletedat字段(gorm.Model 已经包含了该字段)将自动配置为软删除

查找被软删除的记录

Unscoped()方法

1
2
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

永久删除

使用Unscoped()方法

1
2
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;

Delete Flag

默认为删除时间

设置方法见官网

SQL 构建器

原生 SQL(非常好用)

func (db *DB) Raw(sql string, values …interface{}) (tx *DB)

1
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

func (db *DB) Exec(sql string, values …interface{}) (tx *DB)

1
2
3
4
5
6
db.Exec("DROP TABLE users")

db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})

// Exec with SQL Expression
db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")

DryRun 模式

不执行仅生成sql

1
2
3
stmt := db.Session(&Session{DryRun: true}).First(&user, 1).Statement
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
stmt.Vars         //=> []interface{}{1}

ToSQL

返回生成的 SQL 但不执行。

1
2
3
4
sql := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
  return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
})
sql //=> SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10

Row & Rows

获取 *sql.Row 结果

1
2
row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)

获取 *sql.Rows 结果,这是流式传输,返回一个result set,不推荐这么做,一条一条传输网络传输开销太大了

1
2
3
4
5
6
7
8
// 使用 GORM API 构建 SQL
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows()
defer rows.Close()
for rows.Next() {
  rows.Scan(&name, &age, &email)

  // 业务逻辑...
}

将 sql.Rows 扫描至 model

db.ScanRows

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()

var user User
for rows.Next() {
  // ScanRows 将一行扫描至 user
  db.ScanRows(rows, &user)

  // 业务逻辑...
}

连接

在一条 tcp DB 连接中运行多条 SQL (不是事务)

1
2
3
4
5
db.Connection(func(tx *gorm.DB) error {
  tx.Exec("SET my.role = ?", "admin")

  tx.First(&User{})
})

子句(Clause)

GORM 内部使用 SQL builder 生成 SQL。每个操作(会话),GORM 都会创建一个 *gorm.Statement 对象。所有的 GORM API 都是在为 statement 添加、修改 子句,最后,GORM 会根据这些子句生成 SQL

子句构造器

较复杂且我觉得用不到,如果都需要子句构造器了,为什么我不直接使用原生sql(Exex()方法)呢?

关联

开发尽量避免外键的使用

这部分详见官网

Context

WithContext 方法

单会话模式

一个会话执行一个sql命令

1
db.WithContext(ctx).Find(&users)

持续会话模式

一个会话执行多个sql命令

1
2
3
tx := db.WithContext(ctx)
tx.First(&user, 1)
tx.Model(&user).Update("role", "admin")

Context 超时

长 Sql 查询,WithContext传入一个带超时的 context 给 db.

1
2
3
4
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

db.WithContext(ctx).Find(&users)

Hooks/Callbacks 中的 Context

gorm.db.Statement.Context属性

1
2
3
4
5
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  ctx := tx.Statement.Context
  // ...
  return
}

处理错误

GORM 提供链式 API,GORM 会设置 *gorm.DB 的 Error 字段

1
2
3
if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
  // 处理错误...
}

ErrRecordNotFound

找不到记录时,GORM 会返回 ErrRecordNotFound 错误

发生多个错误通过 errors.Is 判断错误是否为 ErrRecordNotFound

1
2
3
// 检查错误是否为 RecordNotFound
err := db.First(&user, 100).Error
errors.Is(err, gorm.ErrRecordNotFound)

链式方法

链式方法:将 Clauses 修改或添加到当前 Statement 的方法。Where, Select, Omit, Joins, Scopes, Preload, Raw(Raw不能和其他的一起用)

终结方法:立即执行注册回调的方法,然后生成并执行 SQL。Create, First, Find, Take, Save, Update, Delete, Scan, Row, Rows

新建会话方法:链式方法, Finisher 方法返回的 *gorm.DB 实例已经被加了各种各样的子句,不能安全地再使用,需要新建一个会话。Session(需要使用NewDB选项)、WithContext、Debug。(另外直接用db重新开启一个会话也是可以的)

会话

gorm.DB.Session()方法传入一个Session对象地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Session 配置
type Session struct {
  DryRun                   bool
  PrepareStmt              bool
  NewDB                    bool
  Initialized              bool
  SkipHooks                bool
  SkipDefaultTransaction   bool
  DisableNestedTransaction bool
  AllowGlobalUpdate        bool
  FullSaveAssociations     bool
  QueryFields              bool
  Context                  context.Context
  Logger                   logger.Interface
  NowFunc                  func() time.Time
  CreateBatchSize          int
}

DryRun

设置为true时生成 SQL 但不执行

预编译

PreparedStmt提高后续的效率

NewDB

NewDB 选项创建一个不带之前条件的新 DB

其余选项见官网

Hook钩子

之前的笔记已经讲过这个内容

详见官网

事务

禁用默认事务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  SkipDefaultTransaction: true,
})

// 持续会话模式
tx := db.Session(&Session{SkipDefaultTransaction: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)

事务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
db.Transaction(func(tx *gorm.DB) error {
  // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回任何错误都会回滚事务
    return err
  }

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

  // 返回 nil 提交事务
  return nil
})

嵌套事务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
db.Transaction(func(tx *gorm.DB) error {
  tx.Create(&user1)

  tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
  })

  tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user3)
    return nil
  })

  return nil
})

// Commit user1, user3

手动事务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 开始事务
tx := db.Begin()

// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
tx.Create(...)

// ...

// 遇到错误时回滚事务
tx.Rollback()

// 否则,提交事务
tx.Commit()

同一个事务需要使用同一个Session(即返回的tx)

SavePoint、RollbackTo

1
2
3
4
5
6
7
8
tx := db.Begin()
tx.Create(&user1)

tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2

tx.Commit() // Commit user1

迁移

官网

Logger

定义logger

1
2
3
4
5
6
7
8
9
newLogger := logger.New(
  log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
  logger.Config{
    SlowThreshold: time.Second,   // 慢 SQL 阈值
    LogLevel:      logger.Silent, // 日志级别
    IgnoreRecordNotFoundError: true,   // 忽略ErrRecordNotFound(记录未找到)错误
    Colorful:      false,         // 禁用彩色打印
  },
)

会话使用logger

1
2
3
4
5
6
7
8
9
// 全局模式
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
  Logger: newLogger,
})

// 新建会话模式
tx := db.Session(&Session{Logger: newLogger})
tx.First(&user)
tx.Model(&user).Update("Age", 18)

日志级别

Silent、Error、Warn、Info

1
2
3
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
  Logger: logger.Default.LogMode(logger.Silent),
})

自定义 Logger

1
2
3
4
5
6
7
type Interface interface {
    LogMode(LogLevel) Interface
    Info(context.Context, string, ...interface{})
    Warn(context.Context, string, ...interface{})
    Error(context.Context, string, ...interface{})
    Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)
}

迁移

AutoMigrate 用于自动迁移您的 schema,保持您的 schema 是最新的。AutoMigrate 会创建表、缺失的外键、约束、列和索引。

1
2
3
4
5
6
db.AutoMigrate(&User{})

db.AutoMigrate(&User{}, &Product{}, &Order{})

// 创建表时添加后缀
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})

常规数据库接口 sql.DB

gorm.DB.DB()方法获取一个标准库中的*sql.DB

1
2
// 获取通用数据库对象 sql.DB,然后使用其提供的功能
sqlDB, err := db.DB()

连接池

sql.DB可以设置连接池配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 获取通用数据库对象 sql.DB ,然后使用其提供的功能
sqlDB, err := db.DB()

// SetMaxIdleConns 用于设置连接池中空闲连接的最大数量。
sqlDB.SetMaxIdleConns(10)

// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)

// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)

设置

Set, Get, InstanceSet, InstanceGet 方法传值给 勾子 或其他方法

Set, Get传递给Session

InstanceSet, InstanceGet传递给同一Statement

数据库索引

index、uniqueIndex 标签

这些索引将在使用 GORM 进行AutoMigrate 或 Createtable 时创建

唯一索引

uniqueIndex 标签

1
2
3
4
type User struct {
    Name1 string `gorm:"uniqueIndex"`
    Name2 string `gorm:"uniqueIndex:idx_name,sort:desc"`
}

复合索引

两个字段使用同一个索引名将创建复合索引

1
2
3
4
5
// create composite index `idx_member` with columns `name`, `number`
type User struct {
    Name   string `gorm:"index:idx_member"`
    Number string `gorm:"index:idx_member"`
}

字段优先级:priority

复合主键

多个字段设为主键,以创建复合主键

1
2
3
4
5
6
type Product struct {
  ID           string `gorm:"primaryKey"`
  LanguageCode string `gorm:"primaryKey"`
  Code         string
  Name         string
}

autoIncrement关闭整形主键自动加1

1
2
3
4
type Product struct {
  CategoryID uint64 `gorm:"primaryKey;autoIncrement:false"`
  TypeID     uint64 `gorm:"primaryKey;autoIncrement:false"`
}

查看当前的sql或执行前打印sql

1
2
3
4
5
//查看当前的sql
tx.Statement.Context

//执行前打印sql,debug方法相当于将当前语句的log等级变为info
db.Debug().xxx
 |