GORM模型(Model)定義
在使用ORM工具時,通常我們需要在代碼中定義模型(Models)與數據庫中的數據表進行映射,在GORM中模型(Models)通常是正常定義的結構體、基本的go類型或它們的指針。 同時也支持sql.Scanner
及driver.Valuer
接口(interfaces)。
GORM 傾向於約定,而不是配置。默認情況下,GORM 使用 ID
作為主鍵,使用結構體名的 蛇形復數
作為表名,字段名的 蛇形
作為列名,並使用 CreatedAt
、UpdatedAt
字段追蹤創建、更新時間
遵循 GORM 已有的約定,可以減少您的配置和代碼量。如果約定不符合您的需求,GORM 允許您自定義配置它們
一、gorm.Model
為了方便模型定義,GORM內置了一個gorm.Model
結構體。gorm.Model
是一個包含了ID
, CreatedAt
, UpdatedAt
, DeletedAt
四個字段的Golang結構體。
// gorm.Model 定義
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
你可以將它嵌入到你自己的模型中:
// 將 `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`字段注入到`User`模型中
type User struct {
gorm.Model
Name string
}
// 等效於
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
當然也可以完全自己定義模型:
// 不使用gorm.Model,自行定義模型
type User struct {
ID int
Name string
}
創建/更新時間追蹤(納秒、毫秒、秒、Time)
GORM 約定使用 CreatedAt
、UpdatedAt
追蹤創建/更新時間。如果您定義了這種字段,GORM 在創建、更新時會自動填充 當前時間
要使用不同名稱的字段,您可以配置 autoCreateTime
、autoUpdateTime
標簽
如果您想要保存 UNIX(毫/納)秒時間戳,而不是 time,您只需簡單地將 time.Time
修改為 int
即可
type User struct {
CreatedAt time.Time // Set to current time if it is zero on creating
UpdatedAt int // Set to current unix seconds on updating or if it is zero on creating
Updated int64 `gorm:"autoUpdateTime:nano"` // Use unix nano seconds as updating time
Updated int64 `gorm:"autoUpdateTime:milli"` // Use unix milli seconds as updating time
Created int64 `gorm:"autoCreateTime"` // Use unix seconds as creating time
}
對於正常的結構體字段,你也可以通過標簽 embedded
將其嵌入,例如:
type Author struct {
Name string
Email string
}
type Blog struct {
ID int
Author Author `gorm:"embedded"`
Upvotes int32
}
// 等效於
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}
並且,您可以使用標簽 embeddedPrefix
來為 db 中的字段名添加前綴,例如:
type Blog struct {
ID int
Author Author `gorm:"embedded;embeddedPrefix:author_"`
Upvotes int32
}
// 等效於
type Blog struct {
ID int64
AuthorName string
AuthorEmail string
Upvotes int32
}
二、字段級權限控制
可導出的字段在使用 GORM 進行 CRUD 時擁有全部的權限,此外,GORM 允許您用標簽控制字段級別的權限。這樣您就可以讓一個字段的權限是只讀、只寫、只創建、只更新或者被忽略
注意: 使用 GORM Migrator 創建表時,不會創建被忽略的字段
type User struct {
Name string `gorm:"<-:create"` // 允許讀和創建
Name string `gorm:"<-:update"` // 允許讀和更新
Name string `gorm:"<-"` // 允許讀和寫(創建和更新)
Name string `gorm:"<-:false"` // 允許讀,禁止寫
Name string `gorm:"->"` // 只讀(除非有自定義配置,否則禁止寫)
Name string `gorm:"->;<-:create"` // 允許讀和寫
Name string `gorm:"->:false;<-:create"` // 僅創建(禁止從 db 讀)
Name string `gorm:"-"` // 通過 struct 讀寫會忽略該字段
}
四、結構體標記(tags)
使用結構體聲明模型時,標記(tags)是可選項。gorm支持以下標記:
4.1 支持的結構體標記(Struct tags)字段標簽
聲明 model 時,tag 是可選的,GORM 支持以下 tag: tag 名大小寫不敏感,但建議使用 camelCase
風格
標簽名 | 說明 |
---|---|
column | 指定 db 列名 |
type | 列數據類型,推薦使用兼容性好的通用類型,例如:所有數據庫都支持 bool、int、uint、float、string、time、bytes 並且可以和其他標簽一起使用,例如:not null 、size , autoIncrement … 像 varbinary(8) 這樣指定數據庫數據類型也是支持的。在使用指定數據庫數據類型時,它需要是完整的數據庫數據類型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
size | 指定列大小,例如:size:256 |
primaryKey | 指定列為主鍵 |
unique | 指定列為唯一 |
default | 指定列的默認值 |
precision | 指定列的精度 |
scale | 指定列大小 |
not null | 指定列為 NOT NULL |
autoIncrement | 指定列為自動增長 |
autoIncrementIncrement | 自動步長,控制連續記錄之間的間隔 |
embedded | 嵌套字段 |
embeddedPrefix | 嵌入字段的列名前綴 |
autoCreateTime | 創建時追蹤當前時間,對於 int 字段,它會追蹤秒級時間戳,您可以使用 nano /milli 來追蹤納秒、毫秒時間戳,例如:autoCreateTime:nano |
autoUpdateTime | 創建/更新時追蹤當前時間,對於 int 字段,它會追蹤秒級時間戳,您可以使用 nano /milli 來追蹤納秒、毫秒時間戳,例如:autoUpdateTime:milli |
index | 根據參數創建索引,多個字段使用相同的名稱則創建復合索引,查看 索引獲取詳情 |
uniqueIndex | 與 index 相同,但創建的是唯一索引 |
check | 創建檢查約束,例如 check:age > 13 ,查看 約束 獲取詳情 |
<- | 設置字段寫入的權限, <-:create 只創建、<-:update 只更新、<-:false 無寫入權限、<- 創建和更新權限 |
-> | 設置字段讀的權限,->:false 無讀權限 |
- | 忽略該字段,- 無讀寫權限 |
comment | 遷移時為字段添加注釋 |
4.2 關聯標簽
標簽 | 描述 |
---|---|
foreignKey | 指定當前模型的列作為連接表的外鍵 |
references | 指定引用表的列名,其將被映射為連接表外鍵 |
polymorphic | 指定多態類型,比如模型名 |
polymorphicValue | 指定多態值、默認表名 |
many2many | 指定連接表表名 |
joinForeignKey | 指定連接表的外鍵列名,其將被映射到當前表 |
joinReferences | 指定連接表的外鍵列名,其將被映射到引用表 |
constraint | 關系約束,例如:OnUpdate 、OnDelete |
五、模型定義示例
package main
import (
"database/sql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
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:"-"` // 忽略本字段
}
func main() {
dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 遷移表創建對應關系
db.AutoMigrate(&User{})
}
修改其中要給字段
package main
import (
"database/sql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
type User struct {
gorm.Model
Name string `gorm:"type:varchar(100)"` // 增減字段限制
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:"-"` // 忽略本字段
}
func main() {
dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 遷移表創建對應關系
db.AutoMigrate(&User{})
}
六、主鍵、表名、列名的約定
6.1 主鍵(Primary Key)
GORM 默認會使用名為ID的字段作為表的主鍵。
type User struct {
ID string // 名為`ID`的字段會默認作為表的主鍵
Name string
}
// 使用`AnimalID`作為主鍵
// Animal 使用`AnimalID`作為主鍵
type Animal struct {
AnimalID int64 `gorm:"primary_key"`
Name string `gorm:"type:varchar(100);comment:姓名"`
Age int64
}
func main() {
dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 遷移表創建對應關系
db.AutoMigrate(&User{}, &Animal{})
}
6.2 表名(Table Name)
表名默認就是結構體名稱的復數,例如:
package main
import (
"database/sql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
type User struct {
gorm.Model
Name string `gorm:"type:varchar(100);comment:姓名"`
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:"-"` // 忽略本字段
}
// TableName 將 User 的表名設置為 `profiles`
func (User) TableName() string {
return "user_randy"
}
func (u User) TableName() string {
if u.Role == "admin" {
return "admin_users"
} else {
return "users"
}
}
func main() {
dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 禁用默認表名的復數形式,如果置為 true,則 `User` 的默認表名是 `user`
db.SingularTable(true)
// 遷移表創建對應關系
db.AutoMigrate(&User{},)
}
也可以通過Table()
指定表名:
// 使用User結構體創建名為`deleted_users`的表
db.Table("deleted_users").CreateTable(&User{})
var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
//// SELECT * FROM deleted_users;
db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
//// DELETE FROM deleted_users WHERE name = 'jinzhu';
GORM還支持更改默認表名稱規則:
gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {
return "prefix_" + defaultTableName;
}
6.3 列名(Column Name)
列名由字段名稱進行下划線分割來生成
type User struct {
ID uint // column name is `id`
Name string // column name is `name`
Birthday time.Time // column name is `birthday`
CreatedAt time.Time // column name is `created_at`
}
可以使用結構體tag指定列名:
type Animal struct {
AnimalId int64 `gorm:"column:beast_id"` // set column name to `beast_id`
Birthday time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
Age int64 `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}
6.3 時間戳跟蹤
CreatedAt
如果模型有 CreatedAt
字段,該字段的值將會是初次創建記錄的時間。
db.Create(&user) // `CreatedAt`將會是當前時間
// 可以使用`Update`方法來改變`CreateAt`的值
db.Model(&user).Update("CreatedAt", time.Now())
UpdatedAt
如果模型有UpdatedAt
字段,該字段的值將會是每次更新記錄的時間。
db.Save(&user) // `UpdatedAt`將會是當前時間
db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`將會是當前時間
DeletedAt
如果模型有DeletedAt
字段,調用Delete
刪除該記錄時,
七、字段默認值
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Users struct {
gorm.Model
Name string `gorm:"type:varchar(100);comment:姓名"`
Age int64
// 設置默認值
}
func main() {
dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 遷移表創建對應關系
db.AutoMigrate(&Users{})
db.Debug().Create(&Users{Name: "randy", Age: 18})
db.Debug().Create(&Users{Age: 18}) // 添加記錄name默認為Null
}
為model設置默認值
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Users struct {
gorm.Model
Name string `gorm:"type:varchar(100);default:RandySun;comment:姓名"`
Age int64
// 設置默認值
}
func main() {
dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 遷移表創建對應關系
db.AutoMigrate(&Users{})
db.Debug().Create(&Users{Age: 19}) // 添加記錄name默認為Null
}
注意:通過tag定義字段的默認值,在創建記錄時候生成的 SQL 語句會排除沒有值或值為 零值 的字段。 在將記錄插入到數據庫后,Gorm會從數據庫加載那些字段的默認值。
舉個例子:
var Users = User{Name: "", Age: 99}
db.Create(&user)
上面代碼實際執行的SQL語句是INSERT INTO users("age") values('99');
,排除了零值字段Name
,而在數據庫中這一條數據會使用設置的默認值RandySun
作為Name字段的值。
注意 所有字段的零值, 比如0
, ""
,false
或者其它零值
,都不會保存到數據庫內,但會使用他們的默認值。 如果你想避免這種情況,可以考慮使用指針或實現 Scanner/Valuer
接口,比如:
使用指針方式實現零值存入數據庫
// 使用指針
type Users struct {
gorm.Model
// 設置默認值
Name *string `gorm:"type:varchar(100);default:RandySun;comment:姓名"`
Age int64
}
db.Debug().Create(&Users{Name: new(string), Age: 19}) // 添加記錄name默認為Null
使用Scanner/Valuer接口方式實現零值存入數據庫
// 使用 Scanner/Valuer
type Users struct {
gorm.Model
//Name *string `gorm:"type:varchar(100);default:RandySun;comment:姓名"`
Name sql.NullString `gorm:"type:varchar(100);default:RandySun;comment:姓名"`
Age int64
// 設置默認值
}
db.Debug().Create(&Users{Name: sql.NullString{String: "", Valid: true}, Age: 19}) // 添加記錄name默認為Null
擴展創建選項
例如PostgreSQL
數據庫中可以使用下面的方式實現合並插入, 有則更新, 無則插入。
// 為Instert語句添加擴展SQL選項
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)
// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;