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.数据库支持
MySQL
、PostgreSQL
、Sqlite3
、SQL Server
对应的数据库驱动可以查看官网或者查看 gorm.Open()
函数查看源码中的驱动注释 。详情见此
注意:使用 MySQL 时,如果想正确处理 time.Time 类型,则需要在 source 字段传中加上 parseTime 参数。
完整示例:
user:password@/dbname?charset=utf8&parseTime=True&loc=Local
2.模型定义
2.1 类型是正常的 golang structs、基本 Go 类型或它们的指针。同时支持 sql.Scanner
和 driver.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
}