Gorm复习

钩子

对象生命周期

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。

如果已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用

如果任何回调返回错误,GORM 将停止后续的操作并回滚事务

在 GORM 中,钩子方法(Hooks)允许你在创建、更新、删除、查询等操作之前或之后执行自定义逻辑

这些钩子方法在对象的生命周期中被自动调用,可以用于设置默认值、验证数据、记录日志等

钩子方法的函数签名应该是 func(*gorm.DB) error

Hook

创建对象

创建时可用的 hook

// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务

代码示例:

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
// 设置uuid
  u.UUID = uuid.New()

  if !u.IsValid() {
  // 验证数据
    err = errors.New("can't save invalid data")
  }
  return
}

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
  if u.ID == 1 {
    tx.Model(u).Update("role", "admin")
  }
  return
}

注意 在 GORM 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果您的钩子返回了任何错误,GORM 会将事务回滚,从而撤销所有在事务中进行的修改

  1. 开始事务:当执行保存或删除操作时,GORM 会自动开始一个事务。
  2. 执行操作:在事务中执行相应的数据库操作,如插入、更新或删除记录。
  3. 钩子方法:在操作前后,GORM 会调用相应的钩子方法(如 BeforeSaveAfterSave 等)。
  4. 错误检查:如果任何钩子方法返回了错误,GORM 会将事务回滚,撤销所有在事务中的操作。
  5. 提交事务:如果所有操作和钩子方法都成功执行,GORM 会提交事务,将更改永久保存到数据库。
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
  if !u.IsValid() {
    return errors.New("rollback invalid user")
  }
  return nil
}

更新对象

更新时可用的 hook

// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务

代码示例:

func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
  if u.readonly() {
  // 检查只读状态
    err = errors.New("read only user")
  }
  return
}

// 在同一个事务中更新数据
func (u *User) AfterUpdate(tx *gorm.DB) (err error) {
  if u.Confirmed {
    tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("verfied", true)
  }
  return
}

删除对象

删除时可用的 hook

// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务

代码示例:

// 在同一个事务中更新数据
func (u *User) AfterDelete(tx *gorm.DB) (err error) {
  if u.Confirmed {
  // 更新关联记录
    tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("invalid", false)
  }
  return
}

查询对象

查询时可用的 hook

// 从 db 中加载数据
// Preloading (eager loading)
AfterFind

代码示例:

func (u *User) AfterFind(tx *gorm.DB) (err error) {
  if u.MemberShip == "" {
    u.MemberShip = "user" // 设置默认值
  }
  return
}

修改当前操作

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
}

事务

禁用默认事务

为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约 30%+ 性能提升

GORM 提供了多种事务处理方式,包括嵌套事务、手动事务、保存点等GORM 提供了多种事务处理方式,包括嵌套事务、手动事务、保存点等

// 全局禁用
db, err := gorm.Open(mysql.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)

事务

要在事务中执行一系列操作,一般流程如下:

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
})

嵌套事务

在嵌套事务中,内部事务的回滚不会影响外部事务的提交

因为嵌套事务实际上是通过保存点(SavePoint)来实现的

当内部事务回滚时,只会回滚到保存点,而不会影响外部事务的其他操作

GORM 支持嵌套事务,您可以回滚较大事务内执行的一部分操作,例如:

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

手动事务

// 开始事务
tx := db.Begin()

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

// ...

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

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

特殊的示例

在函数中处理事务,并使用 defer 确保在发生错误时回滚

func CreateAnimals(db *gorm.DB) error {
  // 事务一旦开始,就应该使用 tx 处理数据
  tx := db.Begin()
  
  // 捕获错误
  defer func() {
    if r := recover(); r != nil {
      tx.Rollback()
    }
  }()

  if err := tx.Error; err != nil {
    return err
  }

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

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

  return tx.Commit().Error
}

SavePoint、RollbackTo

GORM 提供了 SavePointRollbackto 来提供保存点以及回滚至保存点,例如:

tx := db.Begin()
tx.Create(&user1)

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

tx.Commit() // Commit user1

数据库迁移

AutoMigrate

AutoMigrate 用于自动迁移您的 schema,保持您的 schema 是最新的。

注意: AutoMigrate 会创建表,缺少的外键,约束,列和索引,并且会更改现有列的类型(如果其大小、精度、是否为空可更改)。但 不会 删除未使用的列,以保护您的数据。

db.AutoMigrate(&User{})

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

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

注意 AutoMigrate 会自动创建数据库外键约束,您可以在初始化时禁用此功能,例如:

// 禁用外键约束
db, err := gorm.Open(mysql.Open("gorm.db"), &gorm.Config{
  DisableForeignKeyConstraintWhenMigrating: true,
})

Migrator 接口

提供了更细粒度的数据库迁移操作,允许你手动控制表、列、约束和索引的创建、删除和修改

GORM 提供了 Migrator 接口,该接口为每个数据库提供了统一的 API 接口,可用来为您的数据库构建独立迁移,例如:

SQLite 不支持 ALTER COLUMNDROP COLUMN,当你试图修改表结构,GORM 将创建一个新表、复制所有数据、删除旧表、重命名新表。

一些版本的 MySQL 不支持 rename 列,索引。GORM 将基于您使用 MySQL 的版本执行不同 SQL

type Migrator interface {
  // AutoMigrate
  AutoMigrate(dst ...interface{}) error

  // Database
  CurrentDatabase() string
  FullDataTypeOf(*schema.Field) clause.Expr

  // Tables
  CreateTable(dst ...interface{}) error
  DropTable(dst ...interface{}) error
  HasTable(dst interface{}) bool
  RenameTable(oldName, newName interface{}) error

  // Columns
  AddColumn(dst interface{}, field string) error
  DropColumn(dst interface{}, field string) error
  AlterColumn(dst interface{}, field string) error
  HasColumn(dst interface{}, field string) bool
  RenameColumn(dst interface{}, oldName, field string) error
  MigrateColumn(dst interface{}, field *schema.Field, columnType *sql.ColumnType) error
  ColumnTypes(dst interface{}) ([]*sql.ColumnType, error)

  // Constraints
  CreateConstraint(dst interface{}, name string) error
  DropConstraint(dst interface{}, name string) error
  HasConstraint(dst interface{}, name string) bool

  // Indexes
  CreateIndex(dst interface{}, name string) error
  DropIndex(dst interface{}, name string) error
  HasIndex(dst interface{}, name string) bool
  RenameIndex(dst interface{}, oldName, newName string) error
}

当前数据库

返回当前使用的数据库名

dbName := db.Migrator().CurrentDatabase()

// 为 `User` 创建表
db.Migrator().CreateTable(&User{})

// 将 "ENGINE=InnoDB" 添加到创建 `User` 的 SQL 里去
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})

// 检查 `User` 对应的表是否存在
exists := db.Migrator().HasTable(&User{})
db.Migrator().HasTable("users")

// 如果存在表则删除(删除时会忽略、删除外键约束)
db.Migrator().DropTable(&User{})
db.Migrator().DropTable("users")

// 重命名表
db.Migrator().RenameTable(&User{}, &UserInfo{})
db.Migrator().RenameTable("users", "user_infos")

type User struct {
  Name string
}

// 添加 name 字段
db.Migrator().AddColumn(&User{}, "Name")
// 删除 name 字段
db.Migrator().DropColumn(&User{}, "Name")
// 修改 name 字段
db.Migrator().AlterColumn(&User{}, "Name")
// 检查字段是否存在
exists := db.Migrator().HasColumn(&User{}, "Name")

type User struct {
  Name    string
  NewName string
}

// 重命名字段
db.Migrator().RenameColumn(&User{}, "Name", "NewName")
db.Migrator().RenameColumn(&User{}, "name", "new_name")

// 获取字段(列)类型
columns, _ := db.Migrator().ColumnTypes(&User{})
db.Migrator().ColumnTypes(&User{}) ([]*sql.ColumnType, error)

约束

type UserIndex struct {
  Name  string `gorm:"check:name_checker,name <> 'jinzhu'"`
}

// 创建约束
db.Migrator().CreateConstraint(&User{}, "name_checker")

// 删除约束
db.Migrator().DropConstraint(&User{}, "name_checker")

// 检查约束是否存在
exists := db.Migrator().HasConstraint(&User{}, "name_checker")

索引

type User struct {
  gorm.Model
  Name string `gorm:"size:255;index:idx_name,unique"`
}

// 为 Name 字段创建索引
db.Migrator().CreateIndex(&User{}, "Name")
db.Migrator().CreateIndex(&User{}, "idx_name")

// 为 Name 字段删除索引
db.Migrator().DropIndex(&User{}, "Name")
db.Migrator().DropIndex(&User{}, "idx_name")

// 检查索引是否存在
exists := db.Migrator().HasIndex(&User{}, "Name")
db.Migrator().HasIndex(&User{}, "idx_name")

type User struct {
  gorm.Model
  Name  string `gorm:"size:255;index:idx_name,unique"`
  Name2 string `gorm:"size:255;index:idx_name_2,unique"`
}
// 修改索引名
db.Migrator().RenameIndex(&User{}, "Name", "Name2")
db.Migrator().RenameIndex(&User{}, "idx_name", "idx_name_2")

约束

GORM 会在自动迁移和创建表时创建约束,查看 约束数据库索引 获取详情

  • 唯一约束(Unique Constraint):确保字段的值是唯一的

  • 检查约束(Check Constraint):限制字段的值必须满足某些条件

  • 外键约束(Foreign Key Constraint):确保字段的值与另一个表中的主键值一致

    在使用AutoMigrate或CreateTable 时,GORM会根据模型的定义自动创建约束

    使用Migrator接口手动管理约束

    约束的命名规则

    GORM会自动生成约束名称,但可以通过gorm:“constraint” 标签自定义约束名称

    type User struct {
      gorm.Model
      Name string `gorm:"unique;constraint:name_unique"`
    }
    

其他迁移工具

GORM 的 AutoMigrate 在大多数情况下都工作得很好,

更严格的迁移工具,GORM 提供一个通用数据库接口

通过这些工具和接口,可以灵活地管理数据库的迁移过程,确保数据库结构始终与应用程序逻辑保持一致

// returns `*sql.DB`
db.DB()

查看 通用接口 获取详情。

记录日志

Logger

Gorm 有一个 默认 logger 实现,默认情况下,它会打印慢 SQL 和错误

Logger 接受的选项不多,您可以在初始化时自定义它,例如:

newLogger := logger.New(
  log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
  logger.Config{
    SlowThreshold: time.Second,   // 慢 SQL 阈值 超过指定时间会被判定为慢SQL
    LogLevel:      logger.Silent, // Log level
    Colorful:      false,         // 禁用彩色打印
  },
)

// 全局模式
db, err := gorm.Open(mysql.Open("test.db"), &gorm.Config{
  Logger: newLogger,
})

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

日志级别

GORM 定义了这些日志级别:SilentErrorWarnInfo

  • logger.Silent:不输出任何日志
  • logger.Error:只输出错误级别的日志
  • logger.Warn:输出警告和错误级别的日志
  • logger.Info:输出信息、警告和错误级别的日志
  • logger.Debug:输出调试、信息、警告和错误级别的日志
db, err := gorm.Open(mysql.Open("test.db"), &gorm.Config{
  Logger: logger.Default.LogMode(logger.Silent),
})

Debug

Debug 单个操作,将当前操作的 log 级别调整为 logger.Info

// Debug()方法可以用来临时将当前操作的日志级别调整为logger.Info
// 只对当前操作生效,不会影响其他操作的日志级别
db.Debug().Where("name = ?", "jinzhu").First(&User{})
在调试后恢复默认日志级别,可以使用Info()方法
db.Info().Where("name = ?", "jinzhu").First(&User{})

自定义 Logger

参考 GORM 的 默认 logger 来定义您自己的 logger

Logger 需要实现以下接口,它接受 context,所以你可以用它来追踪日志

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() (string, int64), err error)
}

通用数据库接口sql.DB

GORM 提供了 DB 方法,可用于从当前 *gorm.DB 返回一个通用的数据库接口 *sql.DB

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

// Ping
sqlDB.Ping()

// Close
sqlDB.Close()

// 返回数据库统计信息
sqlDB.Stats()

注意 如果底层连接的数据库不是 *sql.DB,它会返回错误

连接池

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

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

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

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

性能

选择字段

默认情况下,GORM 在查询时会选择所有的字段,您可以使用 Select 来指定您想要的字段

db.Select("Name", "Age").Find(&Users{})

或者定义一个较小的 API 结构体,使用 智能选择字段功能

type User struct {
  ID     uint
  Name   string
  Age    int
  Gender string
  // 假设后面还有几百个字段...
}

type APIUser struct {
  ID   uint
  Name string
}

// 根据不同的API结构体动态选择字段,可以使用反射来获取结构体的字段名称
func getColumnsFromStruct(s interface{}) []string {
  t := reflect.TypeOf(s)
  var columns []string
  for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    columns = append(columns, field.Name)
  }
  return columns
}
// 使用示例
columns := getColumnsFromStruct(APIUser{})
db.Model(&User{}).Select(columns).Limit(10).Find(&apiUsers)

// 查询时会自动选择 `id`、`name` 字段
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
// 存储在Find参数的变量中

// 查询时智能选择字段
var apiUsers []APIUser
// 只选择id和name字段
// clause.Columns是一个方便的结构体,用于指定要选择的字段
// 也可以直接使用字符串切片 Select([]string{"id", "name"})
db.Model(&User{}).Select(clause.Columns{Columns:[]string{"id","name"}}).Limit(10).Find(&apiUsers)

迭代、FindInBatches

用迭代或 in batches 查询并处理记录

FindInBatches方法允许你分批查询和处理记录,从而避免一次性加载大量数据导致的内存问题

  // 分批查询和处理记录
  var users []User
  // 指定每批查询的记录数
  batchSize := 10
  //回调函数用于处理每批数据
  db.FindInBatches(&User{}, batchSize, func(tx *gorm.DB, batch int) error {
    // 处理当前批次的数据
    tx.Select(clause.Columns{Columns: []string{"id", "name"}}).Find(&users)
    for _, user := range users {
      println(user.ID, user.Name)
    }
    return nil
  })

Index Hints

Index 用于提高数据检索和 SQL 查询性能。 Index Hints 向优化器提供了在查询处理过程中如何选择索引的信息。与 optimizer 相比,它可以更灵活地选择更有效的执行计划

import "gorm.io/hints"

// 使用索引提示查询
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`)"

// 忽略索引
db.Clauses(
    hints.ForceIndex("idx_user_name", "idx_user_id").ForOrderBy(),
    // 提示优化器忽略指定的索引进行分组操作
    hints.IgnoreIndex("idx_user_name").ForGroupBy(),
).Find(&User{})
// SELECT * FROM `users` FORCE INDEX FOR ORDER BY (`idx_user_name`,`idx_user_id`) IGNORE INDEX FOR GROUP BY (`idx_user_name`)"

读写分离

通过读写分离提高数据吞吐量,查看 Database Resolver 获取详情

自定义数据类型

实现自定义数据类型

Scanner / Valuer

自定义的数据类型必须实现 ScannerValuer 接口,以便让 GORM 知道如何将该类型接收、保存到数据库

例如:

type JSON json.RawMessage

// 实现 sql.Scanner 接口,Scan 将 value 扫描至 Jsonb
// 用于从数据库中扫描值到json.RawMessage类型
func (j *JSON) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
  }
  // 用于表示原始的 JSON 数据。它是一个 []byte 的别名,但提供了额外的方法来处理 JSON 数据
  // 可以使用 json.RawMessage 来存储和检索 JSON 数据
  result := json.RawMessage{}
  err := json.Unmarshal(bytes, &result)
  *j = JSON(result)
  return err
}

// 实现 driver.Valuer 接口,Value 返回 json value
// 用于将json.RawMessage类型转换为数据库值
func (j JSON) Value() (driver.Value, error) {
  if len(j) == 0 {
    return nil, nil
  }
  return json.RawMessage(j).MarshalJSON()
}

type User struct {
  ID    uint
  Name  string
  Attrs JSON `gorm:"type:json"`
}

func main() {
  db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 自动迁移
  db.AutoMigrate(&User{})

  // 插入数据
  db.Create(&User{
    Name:  "jinzhu",
    Attrs: JSON(`{"key": "value"}`),
  })

  // 查询数据
  var user User
  db.First(&user)
  println(user.Name)
  println(string(user.Attrs))
}

有许多第三方包实现了 Scanner/Valuer 接口,可与 GORM 一起使用,例如:

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[]"`
}

GormDataTypeInterface

GORM 会从 type 标签 中读取字段的数据库类型,如果找不到,则会检查该结构体是否实现了 GormDBDataTypeInterfaceGormDataTypeInterface 接口,然后使用接口返回值作为数据类型

type GormDataTypeInterface interface {
  GormDataType() string
}

type GormDBDataTypeInterface interface {
  GormDBDataType(*gorm.DB, *schema.Field) string
}

GormDataType 的结果用于生成通用数据类型,也可以通过 schema.FieldDataType 字段得到。这在 编写插件 或者 hook 时可能会有用,例如:

func (JSON) GormDataType() string {
  return "json"
}

type User struct {
  Attrs JSON
}

func (user User) BeforeCreate(tx *gorm.DB) {
  field := tx.Statement.Schema.LookUpField("Attrs")
  if field.DataType == "json" {
    // 做点什么...
  }
}

在迁移时,GormDBDataType 通常会为当前驱动返回恰当的数据类型,例如:

func (JSON) GormDBDataType(db *gorm.DB, field *schema.Field) string {
  // 使用 field.Tag、field.TagSettings 获取字段的 tag
  // 查看 https://github.com/go-gorm/gorm/blob/master/schema/field.go 获取全部的选项

  // 根据不同的数据库驱动返回不同的数据类型
  switch db.Dialector.Name() {
  case "mysql", "sqlite":
    return "JSON"
  case "postgres":
    return "JSONB"
  }
  return ""
}

如果 struct 没有实现 GormDBDataTypeInterfaceGormDataTypeInterface 接口,GORM 会根据 struct 第一个字段推测其数据类型,例如:会为 NullString 使用 string

type NullString struct {
  String string // 使用第一个字段的数据类型
  Valid  bool
}

type User struct {
  Name NullString // 数据类型会是 string
}

GormValuerInterface

GORM 提供了 GormValuerInterface 接口,支持使用 SQL 表达式或基于 context 的值进行 create/update,例如:

// GORM Valuer interface
type GormValuerInterface interface {
  GormValue(ctx context.Context, db *gorm.DB) clause.Expr
}

使用 SQL 表达式进行 Create/Update
type Location struct {
    X, Y int
}

func (loc Location) GormDataType() string {
  return "geometry"
}

func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
  return clause.Expr{
    SQL:  "ST_PointFromText(?)",
    Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
  }
}

// Scan 方法实现了 sql.Scanner 接口
func (loc *Location) Scan(v interface{}) error {
  // Scan a value into struct from database driver
}

type User struct {
  ID       int
  Name     string
  Location Location
}

db.Create(&User{
  Name:     "jinzhu",
  Location: Location{X: 100, Y: 100},
})
// INSERT INTO `users` (`name`,`point`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

db.Model(&User{ID: 1}).Updates(User{
  Name:  "jinzhu",
  Point: Point{X: 100, Y: 100},
})
// UPDATE `user_with_points` SET `name`="jinzhu",`point`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1

你也可以根据 SQL 表达式进行 create/update,查看 Create From SQL ExprUpdate with SQL Expression 获取详情

基于 Context 的值

如果你想创建或更新一个依赖于当前 context 的值,你也可以实现 GormValuerInterface 接口,例如:

type EncryptedString struct {
  Value string
}

func (es EncryptedString) GormValue(ctx context.Context, db *gorm.DB) (expr clause.Expr) {
  if encryptionKey, ok := ctx.Value("TenantEncryptionKey").(string); ok {
    return clause.Expr{SQL: "?", Vars: []interface{}{Encrypt(es.Value, encryptionKey)}}
  } else {
    db.AddError(errors.New("invalid encryption key"))
  }

  return
}

条件表达式

如果你想构建一些查询 helper,你可以让 struct 实现 clause.Expression 接口:

type Expression interface {
    Build(builder Builder)
}

查看 JSONSQL Builder 获取详情,下面是一个示例:

// 根据 Clause Expression 生成 SQL
db.Find(&user, datatypes.JSONQuery("attributes").HasKey("role"))
db.Find(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))

// MySQL
// SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.role') IS NOT NULL
// SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.orgs.orga') IS NOT NULL

// PostgreSQL
// SELECT * FROM "user" WHERE "attributes"::jsonb ? 'role'
// SELECT * FROM "user" WHERE "attributes"::jsonb -> 'orgs' ? 'orga'

db.Find(&user, datatypes.JSONQuery("attributes").Equals("jinzhu", "name"))
// MySQL
// SELECT * FROM `user` WHERE JSON_EXTRACT(`attributes`, '$.name') = "jinzhu"

// PostgreSQL
// SELECT * FROM "user" WHERE json_extract_path_text("attributes"::json,'name') = 'jinzhu'

04-07
### GORM 的概述 GORM 是一种流行的 Go (Golang) 开源对象关系映射框架,它简化了数据库操作流程并提供了强大的功能支持。通过 GORM,开发者可以轻松实现对多种数据库的支持以及复杂的查询逻辑[^1]。 以下是关于 GORM 使用的一些核心概念和方法: --- ### 数据库连接配置 在使用 GORM 前,需先完成数据库的初始化工作。以下是一个典型的 MySQL 连接示例代码片段: ```go package main import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) func main() { dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("failed to connect database") } // db 已经准备好用于后续的操作 } ``` 上述代码展示了如何利用 `mysql` 驱动程序来创建一个到 MySQL 数据库的连接实例。 --- ### 定义模型结构体 GORM 支持将 Go 结构体映射为数据库中的表。下面展示了一个简单的用户模型定义方式: ```go type User struct { ID uint `json:"id"` Name string `json:"name"` Email string `json:"email"` Password string `json:"-"` } // 自动迁移会基于此结构体生成对应的 SQL 表 db.AutoMigrate(&User{}) ``` 这里需要注意的是,字段标签(如 `json` 和其他自定义标签)可用于控制序列化行为或指定额外属性。 --- ### CRUD 操作基础 #### 创建记录 向数据库插入新数据可以通过调用 `.Create()` 方法完成: ```go user := User{Name: "John Doe", Email: "john@example.com", Password: "secret"} result := db.Create(&user) if result.Error != nil { fmt.Println(result.Error) } else { fmt.Printf("New user created with ID %d\n", user.ID) } ``` #### 查询单条或多条记录 执行简单查询可采用如下形式: ```go var users []User db.Where("age > ?", 18).Find(&users) fmt.Printf("%v\n", users) ``` 对于更复杂的情况,则推荐构建链式条件表达式或者直接编写原生 SQL。 #### 更新现有数据 更新特定列可通过以下方式进行: ```go db.Model(&user).Update("Name", "Jane Doe") // 或者批量修改多个字段 updates := map[string]interface{}{"Age": 25, "Active": true} db.Model(&user).Updates(updates) ``` #### 删除记录 删除某条记录只需调用相应函数即可: ```go db.Delete(&user) ``` 以上即涵盖了基本增删改查的功能介绍。 --- ### 文档与进一步学习资源 官方文档地址位于 https://gorm.io/docs/index.html ,其中包含了详尽的例子和技术细节说明。建议深入阅读以便掌握更多高级特性比如事务处理、预加载关联等。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值