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)
|
批量插入(大量数据时一定要批量插入):
- 将一个 slice 传递给 Create 方法(也是传递引用),这将仅生成一句sql来创建
- 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
另外这些方法都是可以链式调用的(这些方法都返回一个会话对象,属于同一个会话,并且之前方法添加的条件,在后面方法中仍然生效)
有两种将查询结果映射到结构体的方法
- 加tag
- 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
|