跳至正文
Wener Notes

GORM

约 5 分钟阅读

gorm

Golang 的 ORM 实现 基于 Entity 模型

  • 执行过程
    • 构建 Statement 对象
    • 查询 - 执行 query 回调
  • 几乎所有实际操作都是通过 callback 串联起来的
  • callbacks 管理 processor 组
  • processor 下有实际回调处理
  • 回调 操作 Statement 上关联的值对象进行查询修改
    • func(db *gorm.DB)
    • db.Statement.ReflectValue - 结果数据
  • 入口 processor - 调用后开始实际执行
    • create
      • gorm
      • gorm
      • gorm
      • gorm
      • gorm
      • gorm
      • gorm
    • query
      • gorm
      • gorm
      • gorm
    • update
      • 与 create 类似
      • gorm
        - 在 begin_transaction 后
    • delete
      • gorm
      • gorm
      • gorm
      • gorm
      • gorm
      • gorm
    • row
      • gorm
    • raw
      • gorm
  • RegisterDefaultCallbacks - 默认 callback 注册
    • 能看得出来执行过程
  • 关系 - Relationship
    • 创建时会自动创建关联
    • 其他操作可选 - Select(clause.Associations) Select("Profile")
    • 也可以直接针对关联进行操作 - Association("Profile")
    • 关系处理方式分为 JoinTable 和 Reference
  • gorm
    • 多层级 Preload 会按序 - 例如 Preload("Profile.Address") 会分成 Profile 和 Profile.Address 两次完成
    • 如果关系不存在,找不到 Relationship 目前会 NPE
  • 语句构建
    • clause.Expression{ Build(Builder) } - 表示任意语句
    • clause.Builder - WriteString, AddVar, WriteQuoted - 构建上下文
    • clause.Interface - 带 Name 的 表达式 - 可以被合并和替换 - 例如 LIMIT, SELECT
    • clause.Table, clause.Column
  • Relationship 关联的 Schema 可能和实际 Schema 不同 - 导致无法 Preload
  • Embed Struct 也是当作 embedded 来处理的,只不过没有前缀
  • 参考
bash
go get -u gorm.io/gorm

# 基于 cgo 的 sqlite
go get -u gorm.io/driver/sqlite

# 不需要 cgo
go get -u github.com/glebarez/sqlite
go get -u github.com/glebarez/go-sqlite

go get -u gorm.io/driver/postgres
  • 不建议字段设置为 default
    • OnConflict UpdateAll 不会处理
    • Null 无法被读取为 非 Ptr 或 非 sql.Null
tagmeane.g.
column:列名
type:
size:
primaryKey
unique
default:
precision:
scale:
not null
autoIncrement
autoIncrementIncrement:自增步长
embedded
embeddedPrefix:
autoCreateTime
/milli
autoUpdateTime
/milli
index
uniqueIndex
check:
<-可权限: create,update,falsegorm:"<-:create"
->读权限gorm:"->:false"
-忽略字段
comment:
foreignKey:外建名字 - 默认 {ForeignModel}ID
references:被关联对象字段
polymorphic:
polymorphicValue:
many2many:join table
joinForeignKey:
joinReferences:
constraint:OnUpdate,OnDeletegorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"
  • Tag 不分大小写
  • index
    • unique
    • priority
    • class, type, where, comment, expression, sort, collate, option
    • composite
  • uniqueIndex
特殊值
const (
	PrimaryKey   string = "~~~py~~~" // primary key
	CurrentTable string = "~~~ct~~~" // current table
	Associations string = "~~~as~~~" // associations
)
go
import (
  "gorm.io/driver/sqlite"
  _ "github.com/glebarez/sqlite"
  "gorm.io/gorm"
)

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
go
// 直接调用 processor
func TestPreloadOnly(t *testing.T){
  // 模型只包含主键
  var m models.User
  m.ID = 1
  // 通常逻辑 - 但构造出来的 stmt 包含基础信息
  stmt := db.Model(&m).Preload("Profile")
  // 填充需要的信息 - 正常逻辑这些字段会被填充
  stmt.Statement.Dest = stmt.Statement.Model
  stmt.Statement.ReflectValue = reflect.ValueOf(stmt.Statement.Dest).Elem()
  assert.NoError(t, stmt.Statement.Parse(stmt.Statement.Model))

  // 直接调用 preload
  callbacks.Preload(stmt)
  assert.NoError(t, stmt.Error)

  // 字段被成功 preload
  fmt.Println(m.Profile)
}
  • 创建、更新、删除 默认在事务中执行
    • SkipDefaultTransaction 可关闭 - 约 30% 性能
  • Tag 不区分大小写
go
// 查询条件与数据不同但可以一次操作
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)

Model

go
import (
  "github.com/google/uuid"
  "github.com/lib/pq"
)

type Post struct {
  ID     uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
  Title  string
  Tags   pq.StringArray `gorm:"type:text[]"` // 需要指定类型
}

钩子

  • 创建和更新
    • BeforeSave
    • BeforeCreate/BeforeUpdate
    • AfterCreate/AfterUpdate
    • AfterSave
  • 删除
    • BeforeDelete
    • AfterDelete
  • 查询
    • AfterFind
go
// 操作当前的 Statement 可修改语句
func (u *User) BeforeCreate(tx *gorm.DB) error {
  // 通过 tx.Statement 修改当前操作,例如:
  tx.Statement.Select("Name", "Age")
  tx.Statement.AddClause(clause.OnConflict{DoNothing: true})

  // tx 是带有 `NewDB` 选项的新会话模式
  // 基于 tx 的操作会在同一个事务中,但不会带上任何当前的条件
  err := tx.First(&role, "name = ?", user.Role).Error
  // SELECT * FROM roles WHERE name = "admin"
  // ...
  return err
}

Upsert

go
// 更新部分字段
db.Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "id"}},
  DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)

// 从新映射
db.Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "id"}},
  DoUpdates: clause.Assignments(map[string]interface{}{
    "role": "user",
    "count": gorm.Expr("GREATEST(count, VALUES(count))"),
  }),
}).Create(&users)

// 更新所有
db.Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "id"}},
  UpdateAll: true,
}).Create(&users)

DryRun 查看执行语句

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

// 可生成完整 SQL - 不要用于查询,有 SQL 注入风险
db.Dialector.Explain(stmt.SQL.String(), stmt.Vars...)

跳回钩子处理

go
db.Session(&gorm.Session{SkipHooks: true}).Create(&user)

多态

  • 默认多态值为表名
go
type Cat struct {
  ID    int
  Name  string
  Toy   Toy `gorm:"polymorphic:Owner;"`
}

type Dog struct {
  ID   int
  Name string
  Toy  Toy `gorm:"polymorphic:Owner;"`
}

type Toy struct {
  ID        int
  Name      string
  OwnerID   int
  OwnerType string
}

使用 UID 实现类似多态

go
type Model struct {
  ID    int
  UID   string // uuid
}
type Cat struct {
  Model
  Name  string
  Toy   Toy `gorm:"polymorphic:Owner;"`
}

type Dog struct {
  Model
  Name string
  Toy  Toy `gorm:"polymorphic:Owner;"`
}

type Toy struct {
  ID        int
  Name      string
  OwnerID   int
  OwnerType string
  OwnerUID string
}

添加 SQL 函数 默认值

go
type User struct {
    ID          int       `sql:"DEFAULT:myfunction"`
    XID         int       `sql:"type:bigint; DEFAULT:id_generator()"`
    CreatedAt   time.Time `sql:"DEFAULT:current_timestamp"`
}

避免插入空值

go
type User struct {
    gorm.Model
    Email  string    `gorm:"unique;not null;type:varchar(100);default:null"`
}

ErrRecordNotFound

  • First, Last, Take

关联信息

反向链接、本文链接的其他页面和外部资料。

反向链接

  • Go Awesome
    笔记 · gorm

    https://go-proverbs.github.io/ · mux · chi - ⭐️ · gin, mux · ORM · gorm - ⭐️ · 基于反射 · 目前 Go 里实现最为优美的 ORM · XORM 被 gogs 和 gitea 使用,是 gogs 原作者开发 - 但认为比不上 gorm

参考资料

GitHub5 条
最近更新commit 5d788b3编辑