怎樣開通網(wǎng)站網(wǎng)站開發(fā)的一般流程
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):提高開發(fā)效率,防止SQL注入、對不熟悉SQL語句的人友好、代碼統(tǒng)一
- 缺點(diǎn):犧牲執(zhí)行能力、犧牲靈活性、弱化SQL能力
在一些小型項(xiàng)目上使用ORM可以大大提高開發(fā)效率,但是在一些對性能要求高得場景下,ORM可能沒有那么合適。
環(huán)境配置
-
GORM:在命令行中輸入如下命令
go get -u github.com/jinzhu/gorm
即可安裝GORM。 -
MySQL:請參考MySQL安裝教程。
連接數(shù)據(jù)庫
-
GROM官方支持的數(shù)據(jù)庫類型有:MySQL、PostgreSQL, SQLite, SQL Server 和 TiDB。
-
以連接MySQL為例,GORM連接MySQL非常簡單,其代碼如下:
import ("gorm.io/driver/mysql""gorm.io/gorm" )func main() {dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) }
-
MySQL驅(qū)動(dòng)程序也提供了一些高級配置供程序員在初始化的過程中使用。
db, err := gorm.Open(mysql.New(mysql.Config{DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source nameDefaultStringSize: 256, // string 類型字段的默認(rèn)長度DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的數(shù)據(jù)庫不支持DontSupportRenameIndex: true, // 重命名索引時(shí)采用刪除并新建的方式,MySQL 5.7 之前的數(shù)據(jù)庫和 MariaDB 不支持重命名索引DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的數(shù)據(jù)庫和 MariaDB 不支持重命名列SkipInitializeWithVersion: false, // 根據(jù)當(dāng)前 MySQL 版本自動(dòng)配置 }), &gorm.Config{})
模型默認(rèn)相關(guān)
-
GORM提供了一個(gè)預(yù)定義的結(jié)構(gòu)體,名為
gorm.Model
,其中包含常用字段:ID為默認(rèn)的主鍵;CreatedAt記錄了該記錄被創(chuàng)建的時(shí)間;UpdatedAt記錄該記錄被更新的時(shí)間;DeletedAt記錄了該記錄被刪除的時(shí)間,因?yàn)镚ORM中的Delete是軟刪除。// gorm.Model 的定義 type Model struct {ID uint `gorm:"primaryKey"`CreatedAt time.TimeUpdatedAt time.TimeDeletedAt gorm.DeletedAt `gorm:"index"` }
-
將其嵌入結(jié)構(gòu)體中: 可以直接在結(jié)構(gòu)體中嵌入
gorm.Model
,以便自動(dòng)包含這些字段。 這對于在不同模型之間保持一致性并利用GORM內(nèi)置的約定非常有用。當(dāng)然,我們也可以手動(dòng)創(chuàng)建。 -
包含的字段:
ID
:每個(gè)記錄的唯一標(biāo)識符(主鍵)。CreatedAt
:在創(chuàng)建記錄時(shí)自動(dòng)設(shè)置為當(dāng)前時(shí)間。UpdatedAt
:每當(dāng)記錄更新時(shí),自動(dòng)更新為當(dāng)前時(shí)間。DeletedAt
:用于軟刪除(將記錄標(biāo)記為已刪除,而實(shí)際上并未從數(shù)據(jù)庫中刪除)
-
GORM中常見的結(jié)構(gòu)體tag:
標(biāo)簽名 說明 column 指定 db 列名 type 列數(shù)據(jù)類型,推薦使用兼容性好的通用類型,例如:所有數(shù)據(jù)庫都支持 bool、int、uint、float、string、time、bytes 并且可以和其他標(biāo)簽一起使用,例如: not null
、size
,autoIncrement
… 像varbinary(8)
這樣指定數(shù)據(jù)庫數(shù)據(jù)類型也是支持的。在使用指定數(shù)據(jù)庫數(shù)據(jù)類型時(shí),它需要是完整的數(shù)據(jù)庫數(shù)據(jù)類型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
serializer 指定將數(shù)據(jù)序列化或反序列化到數(shù)據(jù)庫中的序列化器, 例如: serializer:json/gob/unixtime
size 定義列數(shù)據(jù)類型的大小或長度,例如 size: 256
primaryKey 將列定義為主鍵 unique 將列定義為唯一鍵 default 定義列的默認(rèn)值 precision 指定列的精度 scale 指定列大小 not null 指定列為 NOT NULL autoIncrement 指定列為自動(dòng)增長 autoIncrementIncrement 自動(dòng)步長,控制連續(xù)記錄之間的間隔 embedded 嵌套字段 embeddedPrefix 嵌入字段的列名前綴 autoCreateTime 創(chuàng)建時(shí)追蹤當(dāng)前時(shí)間,對于 int
字段,它會(huì)追蹤時(shí)間戳秒數(shù),您可以使用nano
/milli
來追蹤納秒、毫秒時(shí)間戳,例如:autoCreateTime:nano
autoUpdateTime 創(chuàng)建/更新時(shí)追蹤當(dāng)前時(shí)間,對于 int
字段,它會(huì)追蹤時(shí)間戳秒數(shù),您可以使用nano
/milli
來追蹤納秒、毫秒時(shí)間戳,例如:autoUpdateTime:milli
index 根據(jù)參數(shù)創(chuàng)建索引,多個(gè)字段使用相同的名稱則創(chuàng)建復(fù)合索引,查看 索引 獲取詳情 uniqueIndex 與 index
相同,但創(chuàng)建的是唯一索引check 創(chuàng)建檢查約束,例如 check:age > 13
,查看 約束 獲取詳情<- 設(shè)置字段寫入的權(quán)限, <-:create
只創(chuàng)建、<-:update
只更新、<-:false
無寫入權(quán)限、<-
創(chuàng)建和更新權(quán)限-> 設(shè)置字段讀的權(quán)限, ->:false
無讀權(quán)限- 忽略該字段, -
表示無讀寫,-:migration
表示無遷移權(quán)限,-:all
表示無讀寫遷移權(quán)限comment 遷移時(shí)為字段添加注釋 -
使用方法如下:
type User struct {gorm.ModelUserName string `gorm:"column:user_name;type:varchar(20);not null"`Age string `gorm:"column:age;type:int"`Email string `gorm:"column:email;type:varchar(20);unique"` }
-
創(chuàng)建記錄
-
我們可以通過
gorm.Create()
方法創(chuàng)建記錄,需要注意的是傳遞的參數(shù)是結(jié)構(gòu)體指針,而非結(jié)構(gòu)體本身。此外,gorm.Create()
方法也可以創(chuàng)建多條記錄。返回值Result
有兩個(gè)屬性,分別為創(chuàng)建記錄錯(cuò)誤Error
和創(chuàng)建記錄的條數(shù)RowAffected
。// 創(chuàng)建一條記錄 user := User{Name: "xiaoming", Age: 21, Email: "XXX@qq.com"} result := db.Create(&user) // 通過數(shù)據(jù)的指針來創(chuàng)建// 創(chuàng)建多條記錄 users := []*User{{Name: "xiaoming", Age: 21, Email: "XXX@qq.com"},{Name: "ming", Age: 21, Email: "YYY@qq.com"}, } result := db.Create(users) // 傳遞切片
-
創(chuàng)建記錄并為指定字段賦值。
db.Select("Name", "Age", "CreatedAt").Create(&user)
-
創(chuàng)建記錄并忽略傳遞給 ‘Omit’ 的字段值
db.Omit("Name", "Age", "CreatedAt").Create(&user)
-
使用鉤子函數(shù):GORM允許用戶通過實(shí)現(xiàn)這些接口
BeforeSave
,BeforeCreate
,AfterSave
,AfterCreate
來自定義鉤子。 這些鉤子方法會(huì)在創(chuàng)建一條記錄時(shí)被調(diào)用。func (u *User) BeforeCreate(tx *gorm.DB) (err error) {u.UUID = uuid.New()if u.Role == "admin" {return errors.New("invalid role")}return }
如果你想跳過
Hooks
方法,可以使用SkipHooks
會(huì)話模式,例子如下DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)
查詢記錄
-
GORM提供了
First
、Take
和Last
方法從數(shù)據(jù)庫中檢索單個(gè)對象。 -
First
andLast
方法會(huì)按主鍵排序找到第一條記錄和最后一條記錄 (分別)。 只有在目標(biāo)struct是指針或者通過db.Model()
指定 model 時(shí),該方法才有效。 此外,如果相關(guān)model沒有定義主鍵,那么將按model的第一個(gè)字段進(jìn)行排序。 例如:var user User var users []User// works because destination struct is passed in db.First(&user) // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1// works because model is specified using `db.Model()` result := map[string]interface{}{} db.Model(&User{}).First(&result) // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1// doesn't work result := map[string]interface{}{} db.Table("users").First(&result)// works with Take result := map[string]interface{}{} db.Table("users").Take(&result)// no primary key defined, results will be ordered by first field (i.e., `Code`) type Language struct {Code stringName string } db.First(&Language{}) // SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
-
如果主鍵是數(shù)字類型,可以使用來檢索對象。 當(dāng)使用字符串時(shí),需要額外的注意來避免SQL注入。
db.First(&user, 10) // SELECT * FROM users WHERE id = 10;db.First(&user, "10") // SELECT * FROM users WHERE id = 10;db.Find(&users, []int{1,2,3}) // SELECT * FROM users WHERE id IN (1,2,3);
如果主鍵是字符串(例如像uuid),查詢將被寫成如下:
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a") // SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
當(dāng)目標(biāo)對象有一個(gè)主鍵值時(shí),將使用主鍵構(gòu)建查詢條件,例如:
var user = User{ID: 10} db.First(&user) // SELECT * FROM users WHERE id = 10;var result User db.Model(User{ID: 10}).First(&result) // SELECT * FROM users WHERE id = 10;
如果對象設(shè)置了主鍵,條件查詢將不會(huì)覆蓋主鍵的值,而是用And連接條件。 例如:
var user = User{ID: 10} db.Where("id = ?", 20).First(&user) // SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1
這個(gè)查詢將會(huì)給出
record not found
錯(cuò)誤 所以,在你想要使用例如user
這樣的變量從數(shù)據(jù)庫中獲取新值前,需要將例如id
這樣的主鍵設(shè)置為nil。 -
檢索全部對象
result := db.Find(&users) // SELECT * FROM users;
-
我們可以使用
Limit
方法限制查詢結(jié)果的條數(shù)result := db.Limit(1).Find(&users) // SELECT * FROM users LIMIT 1;
-
使用struct和map作為條件進(jìn)行查詢
// Struct db.Where(&User{Name: "jinzhu", Age: 20}).First(&user) // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;// Map db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users) // SELECT * FROM users WHERE name = "jinzhu" AND age = 20;// Slice of primary keys db.Where([]int64{20, 21, 22}).Find(&users) // SELECT * FROM users WHERE id IN (20, 21, 22);
-
個(gè)人覺得這一部分和原生的MySQL查詢非常地像,只是將一些條件變成了函數(shù),另外一些條件寫在
Where
方法中作為內(nèi)聯(lián)條件,當(dāng)然,內(nèi)聯(lián)條件也可以寫在First
和Find
中。
更新記錄
-
Save
會(huì)更新所有的字段,即使字段是零值。db.First(&user)user.Name = "jinzhu 2" user.Age = 100 db.Save(&user) // UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
-
Update
可以更新單個(gè)列,Updates
可以更新多列,Updates
方法支持struct
和map[string]interface{}
參數(shù)。使用struct更新時(shí),Updates
將只更新非零字段。// 更新單列 // User 的 ID 是 `111` db.Model(&user).Update("name", "hello") // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;// 更新多列 // 根據(jù) `struct` 更新屬性,只會(huì)更新非零值的字段 db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false}) // UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;// 根據(jù) `map` 更新屬性 db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) // UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
-
在更新時(shí)可以選擇或者忽略某些字段,如果需要選擇某些字段,可以使用
Select
;如果需要忽略某些字段,則可以使用Omit
。// 選擇 Map 的字段 // User 的 ID 是 `111`: db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) // UPDATE users SET name='hello' WHERE id=111;db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;// 選擇 Struct 的字段(會(huì)選中零值的字段) db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0}) // UPDATE users SET name='new_name', age=0 WHERE id=111;// 選擇所有字段(選擇包括零值字段的所有字段) db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})// 選擇除 Role 外的所有字段(包括零值字段的所有字段) db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
-
GORM 支持的 hook 包括:
BeforeSave
,BeforeUpdate
,AfterSave
,AfterUpdate
. 更新記錄時(shí)將調(diào)用這些方法。func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {if u.Role == "admin" {return errors.New("admin user not allowed to update")}return }
-
上述提到的方法均會(huì)調(diào)用鉤子函數(shù),如果想不調(diào)用鉤子函數(shù),則可以使用
UpdateColumn
和UpdateCoulmns
方法,使用方式類似Update
和Updates
。 -
阻止全局更新
如果你執(zhí)行一個(gè)沒有任何條件的批量更新,GORM 默認(rèn)不會(huì)運(yùn)行,并且會(huì)返回
ErrMissingWhereClause
錯(cuò)誤你可以用一些條件,使用原生 SQL 或者啟用
AllowGlobalUpdate
模式,例如:db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClausedb.Model(&User{}).Where("1 = 1").Update("name", "jinzhu") // UPDATE users SET `name` = "jinzhu" WHERE 1=1db.Exec("UPDATE users SET name = ?", "jinzhu") // UPDATE users SET name = "jinzhu"db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu") // UPDATE users SET `name` = "jinzhu"
刪除記錄
-
刪除記錄時(shí)需要指定主鍵,否則會(huì)觸發(fā)批量刪除
// User的ID是1 db.Delete(&user) // DELETE from users where id = 10;// User沒有ID db.Delete(&user) // DELETE from users;
-
當(dāng)然,我們可以使用帶額外條件的刪除避免批量刪除
db.Where("name = ?", "xiaoming").Delete(&user) // DELETE from users where name = "xiaoming";
-
使用鉤子函數(shù)
對于刪除操作,GORM 支持
BeforeDelete
、AfterDelete
等鉤子函數(shù),在刪除記錄時(shí)會(huì)調(diào)用這些方法。func (u *User) BeforeDelete(tx *gorm.DB) (err error) { if u.Role == "admin" { return errors.New("admin user not allowed to delete") } return }
-
阻止全局刪除
當(dāng)你試圖執(zhí)行不帶任何條件的批量刪除時(shí),GORM將不會(huì)運(yùn)行并返回
ErrMissingWhereClause
錯(cuò)誤如果一定要這么做,你必須添加一些條件,或者使用原生SQL,或者開啟
AllowGlobalUpdate
模式,如下例:db.Delete(&User{}).Error // gorm.ErrMissingWhereClausedb.Delete(&[]User{{Name: "jinzhu1"}, {Name: "jinzhu2"}}).Error // gorm.ErrMissingWhereClausedb.Where("1 = 1").Delete(&User{}) // DELETE FROM `users` WHERE 1=1db.Exec("DELETE FROM users") // DELETE FROM usersdb.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{}) // DELETE FROM users
-
軟刪除
如果模型中包含了
gorm.DeleteAt
字段,則模型會(huì)自動(dòng)獲得軟刪除的能力,反之則沒有軟刪除的能力。在有軟刪除能力的情況下調(diào)用
Delete
函數(shù),GORM并不會(huì)從數(shù)據(jù)庫中刪除該記錄,而是將該記錄的DeleteAt
設(shè)置為當(dāng)前時(shí)間,而后的一般查詢方法將無法查找到此條記錄。 -
查找被軟刪除的記錄:可以使用
gorm.Unscoped()
方法實(shí)現(xiàn)。db.Unscoped().Where("age = 21").Find(&users)
-
永久刪除:可以使用
Unscoped
來永久刪除匹配的記錄// User的ID是1 db.Unscoped().Delete(&user) // DELETE FROM order WHERE id=1;
-
刪除標(biāo)志
默認(rèn)情況下,
gorm.Model
使用*time.Time
作為DeletedAt
的字段類型,不過軟刪除插件gorm.io/plugin/soft_delete
同時(shí)也提供其他的數(shù)據(jù)格式支持提示 當(dāng)使用DeletedAt創(chuàng)建唯一復(fù)合索引時(shí),你必須使用其他的數(shù)據(jù)類型,例如通過
gorm.io/plugin/soft_delete
插件將字段類型定義為unix時(shí)間戳等等import "gorm.io/plugin/soft_delete"type User struct {ID uintName string `gorm:"uniqueIndex:udx_name"`DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"` }
- Unix 時(shí)間戳:使用unix時(shí)間戳作為刪除標(biāo)志
import "gorm.io/plugin/soft_delete"type User struct {ID uintName stringDeletedAt soft_delete.DeletedAt }// 查詢 SELECT * FROM users WHERE deleted_at = 0;// 軟刪除 UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;
- 使用
1
/0
作為 刪除標(biāo)志
import "gorm.io/plugin/soft_delete"type User struct {ID uintName stringIsDel soft_delete.DeletedAt `gorm:"softDelete:flag"` }// 查詢 SELECT * FROM users WHERE is_del = 0;// 軟刪除 UPDATE users SET is_del = 1 WHERE ID = 1;
- 混合模式:混合模式可以使用
0
,1
或者unix時(shí)間戳來標(biāo)記數(shù)據(jù)是否被軟刪除,并同時(shí)可以保存被刪除時(shí)間
type User struct {ID uintName stringDeletedAt time.TimeIsDel soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"` // use `1` `0`// IsDel soft_delete.DeletedAt `gorm:"softDelete:,DeletedAtField:DeletedAt"` // use `unix second`// IsDel soft_delete.DeletedAt `gorm:"softDelete:nano,DeletedAtField:DeletedAt"` // use `unix nano second` }// 查詢 SELECT * FROM users WHERE is_del = 0;// 軟刪除 UPDATE users SET is_del = 1, deleted_at = /* current unix second */ WHERE ID = 1;
參考文章
- [【官方文檔】GORM指南](GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.)