GO语言ORM之GORM

GO语言ORM之GORM

一.GORM 简介

定义:The fantastic ORM library for Golang, aims to be developer friendly.

特性:

  • Full-Featured ORM (almost)
  • Associations (Has One, Has Many, Belongs To, Many To Many, Polymorphism)
  • Hooks (Before/After Create/Save/Update/Delete/Find)
    Preloading (eager loading)
  • Transactions
  • Composite Primary Key
  • SQL Builder
  • Auto Migrations
  • Logger
  • Extendable, write Plugins based on GORM callbacks
  • Every feature comes with tests
  • Developer Friendly

二.GORM 安装

go get -u -v github.com/jinzhu/gorm 

注意:如果项目是 Go module 模式,请慎重使用 -u 参数,因为它可能影响项目中 go.mod 文件中其他 module 的版本依赖。可以使用 go help get 查看 -u 参数的详情说明,也可参考 Go 包管理文章中对 module 模式的说明。

三.GORM 使用

1.数据库支持

MySQLPostgreSQLSqlite3SQL Server 对应的数据库驱动可以查看官网或者查看 gorm.Open() 函数查看源码中的驱动注释 。详情见此

注意:使用 MySQL 时,如果想正确处理 time.Time 类型,则需要在 source 字段传中加上 parseTime 参数。

完整示例:user:password@/dbname?charset=utf8&parseTime=True&loc=Local

2.模型定义

2.1 类型是正常的 golang structs、基本 Go 类型或它们的指针。同时支持 sql.Scannerdriver.Valuer 接口。

示例:

type User struct {
    gorm.Model 
    Name string 
    Age sql.NullInt64
    Birthday *time.Time
    Email        string  `gorm:"type:varchar(100);unique_index"`
    Role         string  `gorm:"size:255"` // 设置字段大小为255
    MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
    Num          int     `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
    Address      string  `gorm:"index:addr"` // 给address字段创建名为addr的索引
    IgnoreMe     int     `gorm:"-"` // 忽略本字段
}
2.2 结构体标记
结构体标记(Tag) 描述
Column 指定列名
Type 指定列数据类型
Size 指定列大小, 默认值255
PRIMARY_KEY 将列指定为主键
UNIQUE 将列指定为唯一
DEFAULT 指定列默认值
PRECISION 指定列精度
NOT NULL 将列指定为非 NULL
AUTO_INCREMENT 指定列是否为自增类型
INDEX 创建具有或不带名称的索引, 如果多个索引同名则创建复合索引
UNIQUE_INDEX INDEX 类似,只不过创建的是唯一索引
EMBEDDED 将结构设置为嵌入
EMBEDDED_PREFIX 设置嵌入结构的前缀
- 忽略此字段

3.GORM的一些约定

3.1 内嵌结构 gorm.Model
// gorm.Model 定义
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}
3.2 默认会使用名为 ID 的字段作为表的主键。
type User struct {
    ID uint64 // 默认作为表的主键 
    Name string
}
3.3 表名默认是结构体名称的复数
type User struct {} // 默认表名是 `users`

// 将 User 的表名设置为 `profiles`
func (User) TableName() string {
  return "profiles"
}

// 禁用默认表名的复数形式,但是如果结构体有 TableName 方法则不受影响。
db.SingularTable(true) 

// 使用 User 结构指定表名为 haha_user。
db.Table("haha_user").CreateTable(&User{})
3.4 下划线分割列字段名,可使用标记指定列名
type User struct {
    ID int // 列名为 id
    Name string // 列名为 name
    CreateAt time.Time // 列名为 create_at
    Age int64 `gorm:"column:age_young` // 列名为 age_young
}
3.5 时间点(Timestamp),如果模型有 CreatedAt 字段,该字段的值为首次创建记录的时间;如果模型有 UpdatedAt 字段,该字段为每次更新记录的时间;如果模型有 DeletedAt 字段,当调用 Delete 删除该记录是,会将 DeletedAt 字段设置为当前时间,表示该记录已经从数据库中删除,但是数据库仍然存在该记录,这是软删除。

4.CRUD 操作

注意:所有的 CRUD 操作,传入的参数都应该是类型的指针。否则会操作失败!

4.1 创建
type User struct {
    ID int
    Name string
}

user := User{Name: "hello"}
db.Create(&user) 

注意:所有字段的零值都不会更新到数据库中,但是会使用该字段的默认值。若想避免这种情况,因该考虑使用指针或实现 Scanner/Valuer 接口。

4.2 查询
// 根据主键查询第一条记录
db.First(&user)

// 随机获取一条记录
db.Take(&user)

// 根据主键查询最后一条记录
db.Last(&user)

// 查询所有的记录
db.Find(&users)

// 查询指定的某条记录(仅当主键为整型时可用)
db.First(&user, 10)

增加 Where 条件:

// 符合条件的所有记录
db.Where("name = ?", "hello").Find(&user)

// 模糊查询 LIKE
db.Where("name LIKE ?", "%jin%").Find(&user)

// IN
db.Where("name IN (?)", []string{"jinzhu", "jinzhu 2"}).Find(&user)

// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)

// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&user)

// 主键切片
db.Where([]int64{20, 21, 22}).Find(&user)

注意:当通过结构体进行查询时,GORM将会只通过非零值字段查询,这意味着如果你的字段值为0,’’, false 或者其他 零值时,将不会被用于构建查询条件,例如

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&user)
//// SELECT * FROM users WHERE name = "jinzhu";

同上可以设置类型为指针或者使用接口来避免这个问题。

4.3 更新
// save 更新所有字段。当你没有为字段赋值时,则为字段的默认值。
// 当 user 没有主键时,会插入一条新的数据。
db.Save(&user)

// 更新单个属性,如果它有变化
db.Model(&user).Update("name", "hello")

// 更新选定字段
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})

// 忽略选定字段
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})

注意: 在使用 struct 取更新字段时,只会更新其中值有变化且为非零值的字段。可以使用 map 来更新使用零值的字段!

4.4 删除
// 删除现有记录,要确保 email 的主键有值,否则会删除所有的记录
db.Delete(&email)

// 物理删除,如果表中存在 DeletedAt 字段,可以使用该函数从数据库中删除该条记录,否则则是在该字段记录下删除的时间,而数据库中仍然存在该调记录。
db.Unscoped().Delete(&order)

5.GORM操作说明

5.1 链式操作
db.Where("name = ?","a")

db.Where("age = ?", 1)

db.Find(&user) // 该查询会将上面的条件加到一起

所有的链式操作都是线程安全的。

5.2错误处理
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {  // error 处理...
}

// 如果发生了一个以上的错误, `GetErrors` 以`[]error`形式返回他们
errors := db.First(&user).Limit(10).Find(&users).GetErrors()

fmt.Println(len(errors))

for _, err := range errors {
  fmt.Println(err)
}

// 检查是否为 RecordNotFound 错误
db.Where("name = ?", "hello world").First(&user).RecordNotFound()

if db.Model(&user).Related(&credit_card).RecordNotFound() {
  // 未找到记录
}

if err := db.Where("name = ?", "jinzhu").First(&user).Error; gorm.IsRecordNotFoundError(err) {
  // 未找到记录
}
5.3 事务
func CreateAnimals(db *gorm.DB) error {
  return db.Transaction(func(tx *gorm.DB) error {
    // 在事务中做一些数据库操作 (这里应该使用 'tx' ,而不是 'db')
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
      // 返回任意 err ,整个事务都会 rollback
      return err
    }

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

    // 返回 nil 提交事务
    return nil
  })
}

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
}

 上一篇
EveryDay English EveryDay English
Everyday EnglishYear: 2020 Month: 05Day: 11 单词 翻译 fetch 取 integrate 整合 schema 概要,架构,结构 retrieve 取回,检索 origi
2020-05-13
下一篇 
Go包管理之modules Go包管理之modules
Go包管理之modulesGo 包管理发展进程​ 在 Go 语言出世之时还未为其设计包管理工具,所以在最初开发之时,包管理一直是它的一个痛点。 在 1.5 版本之前,所有的依赖包都是放在 GOPATH 之下的,并且没有版本控制。这会
2020-04-24
  目录