GORM模型(Model)定義


GORM模型(Model)定義

在使用ORM工具時,通常我們需要在代碼中定義模型(Models)與數據庫中的數據表進行映射,在GORM中模型(Models)通常是正常定義的結構體、基本的go類型或它們的指針。 同時也支持sql.Scannerdriver.Valuer接口(interfaces)。

GORM 傾向於約定,而不是配置。默認情況下,GORM 使用 ID 作為主鍵,使用結構體名的 蛇形復數 作為表名,字段名的 蛇形 作為列名,並使用 CreatedAtUpdatedAt 字段追蹤創建、更新時間

遵循 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 約定使用 CreatedAtUpdatedAt 追蹤創建/更新時間。如果您定義了這種字段,GORM 在創建、更新時會自動填充 當前時間

要使用不同名稱的字段,您可以配置 autoCreateTimeautoUpdateTime 標簽

如果您想要保存 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 nullsize, 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 關系約束,例如:OnUpdateOnDelete

五、模型定義示例

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

}

image-20211120192155198

修改其中要給字段

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

}

image-20211120192444934

image-20211120192518753

六、主鍵、表名、列名的約定

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

image-20211120193350811

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

}

image-20211120194727425

也可以通過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

}

image-20211120201553441

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

image-20211120214853188

image-20211120201607641

注意:通過tag定義字段的默認值,在創建記錄時候生成的 SQL 語句會排除沒有值或值為 零值 的字段。 在將記錄插入到數據庫后,Gorm會從數據庫加載那些字段的默認值。

舉個例子:

var Users = User{Name: "", Age: 99}
db.Create(&user)

上面代碼實際執行的SQL語句是INSERT INTO users("age") values('99');,排除了零值字段Name,而在數據庫中這一條數據會使用設置的默認值RandySun作為Name字段的值。

image-20211120215532229

注意 所有字段的零值, 比如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

image-20211120220135920

image-20211120220111427

使用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

image-20211120220904299

image-20211120220830798

image-20211120220348541

擴展創建選項

例如PostgreSQL數據庫中可以使用下面的方式實現合並插入, 有則更新, 無則插入。

// 為Instert語句添加擴展SQL選項
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)
// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM