Web 三件套之 Gorm | 青训营笔记

223 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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"
}
  1. 默认使用名为 ID 的键作为主键
  2. 默认使用结构体名蛇形复述作为表名
  3. 字段名的蛇形命名作为列名
  4. 使用CreatedAtUpdatedAt作为创建时间、修改时间
  5. 上述结构体标签用于覆盖上述约定

CRUD

Create

  使用Create()方法插入新数据,如:

// 插入一条数据
db.Create(&UserInfo{
    ID:    1,
    Name:  "Maomao",
    Phone: "12345678901",
})
// 插入多条数据时可直接传入一个切片
  1. 使用db.Select().Create()指定要插入的列

    使用db.Omit().Create()跳过要插入的列

  2. 支持使用map[string]interface{}map[string]interface{}{}插入

  3. 不填写主键且设置主键自增后可从原结构体获取主键值

  4. 可使用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)
  1. First()在查询不到数据时会产生ErrRecondNotFound错误,Find()不会。
  2. 使用 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", "齐大大")
  1. 更新多列可使用structmap[string]interface{},但前者之更新非零列。

  2. 可使用Select()强制指定要更新的列

  3. 可使用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 )阅读