这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
重点内容
- Gorm 创建连接与初始化
- CRUD 操作
- Gorm 中的事务操作
- Hook 的使用
Gorm
官方文档/网站:gorm.cn/
连接&初始化
目前 Gorm 支持 MySQL 、 SQLite 、 SQL Server 、PostgreSQL 四种数据库系统,下面以MySQL为例进行介绍:
导入依赖&数据库连接:
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
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{})
}
定义表的结构(Gorm model):
type UserInfo struct{
ID uint `gorm:"primaryKey;column:id"`
Name string `gorm:"column:name"`
Phone string `gorm:"default:12345678901"`
}
// 自定义表明
func (ui UserInfo) TableName() string {
return "user_info"
}
- 默认使用名为 ID 的键作为主键
- 默认使用结构体名蛇形复述作为表名
- 字段名的蛇形命名作为列名
- 使用
CreatedAt、UpdatedAt作为创建时间、修改时间- 上述结构体标签用于覆盖上述约定
CRUD
Create
使用Create()方法插入新数据,如:
// 插入一条数据
db.Create(&UserInfo{
ID: 1,
Name: "Maomao",
Phone: "12345678901",
})
// 插入多条数据时可直接传入一个切片
使用
db.Select().Create()指定要插入的列使用
db.Omit().Create()跳过要插入的列支持使用
map[string]interface{}或map[string]interface{}{}插入不填写主键且设置主键自增后可从原结构体获取主键值
可使用
Clauses(clause.OnConflict{DoNothing: true})指定主键冲突时不进行操作(与Save()相反)
Read
可以使用First()(主键升序)、Take()(数据库内存储顺序)或Last()(主键降序)根据条件查询一条数据(LIMIT 1),如:
var ui UserInfo
// 查询主键升序第一条
db.First(&ui)
// 对整型主键进行查询(不安全)
db.First(&db, 2)
// 对主键外的字段进行条件查询(不能查主键)
db.First(&ui, "name = ?", "Maomao")
若需要查询的所有结果,使用Find(),用法与First()类似,如:
var uis []UserInfo
db.Find(&uis, "`name` like ?", "%M%")
查询条件也可拆成多个函数,如上述Find()的例子可拆成如下形式:
db.Where("`name` like ?", "%M%").Find(&uis)
First()在查询不到数据时会产生ErrRecondNotFound错误,Find()不会。- 使用 struct 作为查询条件,会忽略零值
除了Where(),还有Not()、Or()可对查询结果行添加筛选条件;Select()可对查询列添加筛选条件;Limit()和Offset()对结果条数添加条件等。
Update
可使用Save()根据主键更新(包括零值)或插入记录:
newUi := UserInfo{
ID: 4,
Name: "齐大",
Phone: "12345678901",
}
db.Save(newUi)
更新部分列使用Update(),使用Where()添加限制条件,使用Table()指定表名或使用Model()指定表名和主键:
db.Table("user_info").Where("id = ?", "4").Update("name", "齐大大")
更新多列可使用
struct或map[string]interface{},但前者之更新非零列。可使用
Select()强制指定要更新的列可使用
gorm.Expr()编写表达式更新,如:db.Model(&User{ID: 4,}).Update("age", gorm.Expr("age + ?"), 1)
Delete
使用Delete()来删除记录:
// 根据主键删除
db.Delete(&UserInfo{}, 4)
// 根据传入对象的主键删除
db.Delete(&ui)
// 根据附加条件删除
db.Where("name = ?", "齐大").Delete(&UserInfo{})
db.Delete(&UserInfo{}, "name = ?", "齐大")
Gorm 拒绝通过不加限制条件来删库,需要添加一些添加条件,如:
// Wrong
db.Delete(&UserInfo{})
// Right
db.Where("1 = 1").Delete(&UserInfo{})
事务
在 Gorm 开启一个事务使用Begin(),提交使用Commit(),回滚使用Rollback(),一个简单的示例如下:
tx := db.Begin()
if err = tx.Create(&UserInfo{Name: "Maomao", Phone: "13261534257"}).Error; err != nil {
tx.Rollback()
return
}
if err = tx.Create(&UserInfo{Name: "Monroe", Phone: "13261634257"}).Error; err != nil {
tx.Rollback()
return
}
tx.Commit()
上述操作可使用Transaction()函数简化:
db.Transaction(func(tx *gorm.DB) error {
if err = tx.Create(&UserInfo{Name: "Maomao", Phone: "13261534257"}).Error; err != nil {
return err
}
if err = tx.Create(&UserInfo{Name: "Monroe", Phone: "13261634257"}).Error; err != nil {
return err
}
return nil
})
Hook
可通过实现 Hook 函数在 CRUD 前后进行自动化操作,如:
func (u *UserInfo) BeforeCreate(tx *gorm.DB) (err error) {
if len(u.Phone) != 11 {
return error("Invalid data")
}
return nil
}
Gram会使用默认事务执行 Hook 和单条语句,在 Hook 中返回异常会使事务回滚。对于不同的操作,Hook的调用顺序如下:
-
Create:
- 开始事务
- BeforeSave
- BeforeCreate
- 关联前的 save
- 插入记录至 db
- 关联后的 save
- AfterCreate
- AfterSave
- 提交或回滚事务
-
Read
- 从 db 中加载数据
- Preloading (eager loading)
- AfterFind
-
Update
- 开始事务
- BeforeSave
- BeforeUpdate
- 关联前的 save
- 更新 db
- 关联后的 save
- AfterUpdate
- AfterSave
- 提交或回滚事务
-
Delete
- 开始事务
- BeforeDelete
- 删除 db 中的数据
- AfterDelete
- 提交或回滚事务
性能提升&补充
为了保证数据安全,Gorm 对于单独的操作开启了默认事务,若不需要关联操作和 Hook 可考虑关闭默认事务,即在创建连接时添加SkipDefaultTransaction: true。
可通过开启 SQL 语句预编译来提升性能,即在创建连接时添加PrepareStmt: true
本文若有不足之处,欢迎纠正(≧^.^≦)喵~
我的其他笔记,可在掘金或 Github( github.com/DoudiNCer/IWnA )阅读