前言
ORM全稱Object Relational Mapping,是把編程語言中的Object/Struct數據類型映射到關系數據庫中1張表,以下是詳細映射關系。
結構體------------ 數據表 結構體實例---------- 表中1條記錄 結構體字段------------ 結構體字段
gorm簡介
面向github編程找一找Golang中比較流行的orm,
注意Django的orm是包含在Django web框架中的,而gorm有點像Python中的sqlalchemy它不限制你必須要在某個web框架中使用它。
gorm中文官方網站內含十分齊全的中文文檔。
本文將圍繞當前最近版本gorm v1.20.8展開。
go.mod
module golang-gorm go 1.14 require ( github.com/go-sql-driver/mysql v1.5.0 gorm.io/driver/mysql v1.0.3 gorm.io/gorm v1.20.8 )
gorm安裝
SET GOPROXY=https://goproxy.cn
go get -u github.com/jinzhu/gorm
連接數據庫
在golang中連接不同的數據就需要使用不同的driver驅動。
GORM 官方支持的數據庫類型有: MySQL, PostgreSQL, SQlite, SQL Server
"gorm.io/driver/mysql" //MySQL驅動
"gorm.io/driver/sqlite"//sqllite的驅動
"gorm.io/driver/postgres" //postgres的驅動
開始
package main import ( "fmt" "gorm.io/driver/mysql" //MySQL驅動 "gorm.io/gorm" "time" ) func init() { //配置數據連接:注意設置parseTime=true否則會報錯!unsupported Scan 時間類型的字段 dbinfo := "zhanggen:xxoo@tcp(192.168.56.18:3306)/web?charset=utf8&parseTime=true&loc=Local" db, err := gorm.Open(mysql.Open(dbinfo), &gorm.Config{}) //配置一下數據連接參數! mySQL, err := db.DB() if err != nil { fmt.Println(err) } defer mySQL.Close() //設置最大空閑連接 mySQL.SetMaxIdleConns(10) //設置最大連接數 mySQL.SetMaxOpenConns(100) //設置連接超時時間:1分鍾 mySQL.SetConnMaxLifetime(time.Minute) }
Model定義
我們在創建Table的時可以通過設置struct字段的tag,對數 表名、字段進行屬性設置。
結構體
package book import "time" type Book struct { ID uint `gorm:"column:id;primaryKey"` Title string `gorm:"column:title;not null;type:varchar(100);unique_index"` //指定數據庫列的數據類型 Author string `gorm:"column:author;not null;size:125"` //設置Author在數據庫中不能為空,字段大小為255 Publishtime *time.Time `gorm:"column:publishtime"` } // 將默認表面Book 的表名設置為book func (Book) TableName() string { return "book" }
--------------------------------
package login //用戶表 type UserInfo struct { ID uint `gorm:"column:id;primaryKey"` Username string `gorm:"column:username;unique;not null;index"` Password string `gorm:"column:password;not null;"` } // 將 UserInfos 的表名設置為 `user` func (UserInfo) TableName() string { return "user" }
Mysql
MariaDB [web]> desc book; +-------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | title | varchar(100) | NO | | NULL | | | author | varchar(125) | NO | | NULL | | | publishtime | datetime(3) | YES | | NULL | | +-------------+---------------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
-------------------------------------
MariaDB [web]> desc user; +----------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | username | varchar(191) | NO | UNI | NULL | | | password | longtext | NO | | NULL | | +----------+---------------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec)
Django orm的差別
當我們Django的model中修改了表字段需要先make magrations生成操作,然后再make magirate去數據庫真正執行這次magration。保證了數據庫的安全。
而gorm只要你執行db.AutoMigrate()就會實時修改表結構,但是當你使用Django orm 修改了數據庫字段magration時經常報錯Exist table讓你不知所措。
查看gorm操作日志
db.Debug可以幫助我們顯示gorm對數據庫的操作
db.Debug().AutoMigrate(&User{})
日志內容
[1.000ms] [rows:0] ALTER TABLE `users` MODIFY COLUMN `age` bigint DEFAULT 19
增刪改查CURD
立即執行方法(Immediate Methods):查詢只有執行了立即執行函數才會去數據庫查詢 First/Find/FirstrOrInit/FirstOrCreate/
內聯條件 inline condition:就是把SQL查詢條件當做參數傳遞到立即執行函數中執行。
gorm支持使用String、Struct&Map作為條件進行查詢已經鏈樣操作。
增
創建單條記錄
/1.創建單條記錄 u := User{Name: "Martin", Age: 19} result := Model.Create(&u) //返回插入數據的主鍵 fmt.Println(u.ID) //返回的error fmt.Println(result.Error) //返回插入記錄的條數 fmt.Println(result.RowsAffected) //如果記錄存在將不再重復創建 result = Model.FirstOrCreate(&u) if result.RowsAffected == 0 { fmt.Println("記錄已經存在!") }
批量創建
在生產環境之中如果你插入數據特別頻繁,可以使用批量插入的方式進行優化。如果數據沒有實時展示的要求。
//批量創建 var userList = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} Model.Debug().Create(&userList) for _, user := range userList { fmt.Println(user.ID) // 1,2,3 }
查詢
new和make區別:make給map/channel/slince申請內存和容量返回值類型,new初始化基本類型string/int/結構體返回指針類型的變量
主鍵查詢
Objects can be retrieved using primary key by using Inline Conditions.
Be extra careful with strings to avoid SQL Injection, check out Security section for details
通過內聯條件的方式,我們可以根據主鍵獲取到數據庫中的1/多條記錄。
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);
一般查詢
//1.普通查詢 u := new(User) //查詢第1條數據 Model.Debug().First(u) //SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1 fmt.Println(u.Name) Model.Debug().First(u, 10) fmt.Println(u) //隨機獲取1條記錄 u = new(User) //new和make區別:make給map/channel/slince申請內存和容量返回值類型,new初始化基本類型string/int/結構體返回指針類型的變量 Model.Debug().Take(u) //SELECT * FROM `users` LIMIT 1 fmt.Println(u.Name) //獲取最后1條記錄 u = new(User) Model.Last(u) fmt.Println(u.Name) //查詢表中所有記錄 userList := []User{} //SELECT * FROM `users` Model.Debug().Find(&userList) fmt.Println(userList)
Where 條件查詢
//查詢所有匹配的記錄 然后獲取第1條 u=new(User) Model.Debug().Where("name=?","jinzhu1").First(&u)//SELECT * FROM `users` WHERE name='jinzhu1' ORDER BY `users`.`id` LIMIT 1 fmt.Println(u.Name) //查詢所有匹配的記錄,獲取所有 userList=[]User{} Model.Debug().Where("name=?","Martin").Find(&userList) //SELECT * FROM `users` WHERE name='Martin' fmt.Println(userList) userList=[]User{} Model.Debug().Where("name <>?","Martin").Find(&userList) //SELECT * FROM `users` WHERE name <>'Martin' fmt.Println(userList) userList=[]User{} Model.Debug().Where("name in?",[]string{"jinzhu"}).Find(&userList) //SELECT * FROM `users` WHERE name in('jinzhu') fmt.Println(userList)
使用Struct & Map & 切片進行查詢
我們可使用結構體、map、slice把搜索條件組合起來。
//struct u = new(User) Model.Debug().Where(User{Name: "曹操", Age: 29}).First(&u) fmt.Println(u.Name) userList=[]User{} //map:我們可以使用map接收前端的json然后做組合搜索 Model.Debug().Where(map[string]interface{}{"name": "希拉里", "age": 29}).Find(&userList) //SELECT * FROM `users` WHERE `users`.`name` = '曹操' AND `users`.`age` = 29 ORDER BY `users`.`id` LIMIT 1 fmt.Println(userList) //主鍵的切片 userList=[]User{} Model.Where([]int64{1,2,4,6}).Find(&userList) fmt.Println(userList)
Not 條件查詢
我們可以使用Where指定篩選條件,也可以使用Not排除一些條件。
//not條件查詢 u = new(User) userList = []User{} //1.不等於 <> //SELECT * FROM `users` WHERE `name` <> 'jinzhu' ORDER BY `users`.`id` Model.Debug().Not("name", "jinzhu").First(&u) fmt.Println(u.Name) //SELECT * FROM `users` WHERE `name` <> 'Martin' Model.Debug().Not("name", "Martin").Find(&userList) fmt.Println(userList) //2.不在x范圍之類 Not In userList = []User{} //SELECT * FROM `users` WHERE `users`.`id` NOT IN (1,4) Model.Debug().Not([]int64{1, 4}).Find(&userList) fmt.Println(userList) //SELECT * FROM `users` userList = []User{} Model.Debug().Not([]int64{}).Find(&userList) fmt.Println(userList) //4.執行原生Sql userList = []User{} Model.Debug().Not("name=? or age<10", "Martin").Find(&userList) fmt.Println(userList) //5.not和where一樣同樣支持 通過struct/map/切片來組裝搜索條件 userList = []User{} Model.Debug().Not(User{Name: "Martin", Age: 19}).Find(&userList) fmt.Println(userList) userList = []User{} //SELECT * FROM `users` WHERE (`age` <> 19 AND `name` <> 'jinzhu') Model.Debug().Not(map[string]interface{}{"name": "jinzhu", "age": 19}).Find(&userList) fmt.Println(userList) userList = []User{} Model.Debug().Not([]int64{1, 2}).Find(&userList) //SELECT * FROM `users` WHERE `users`.`id` NOT IN (1,2) fmt.Println(userList)
Or條件查詢
我可以使用Or連接2個搜索條件。
//Or條件 userList = []User{} Model.Where("name=?", "Martin").Or("name=?", "jinzhu").Find(&userList) fmt.Println(userList) //Or也支持Struct、map、slice進行條件組成搜索 userList = []User{} //SELECT * FROM `users` WHERE `users`.`name` = 'Martin' AND `users`.`age` = 29 OR (`users`.`name` = 'jinzhu' AND `users`.`age` = 29) Model.Debug().Where(User{Name: "Martin", Age: 29}).Or(User{Name: "jinzhu", Age: 29}).Find(&userList) fmt.Println(userList) //SELECT * FROM `users` WHERE `users`.`id` IN (1,2) OR `users`.`id` IN (5,4) Model.Debug().Where([]int64{1, 2}).Or([]int64{5,4}).Find(&userList) fmt.Println(userList)
內聯條件查詢
通過以上大量的實驗你會發現我總會在Where/Not/Or里面組織搜索條件,然后再 .First/Find。
這是Where/Not/Or中的SQL需要借助立即執行方法 才真正把SQL發送到mysqld(服務端)執行,然后返回值,我們聲明的變量才會獲取到值。
您會發現.Find()和.First()t這些立即執行方法可以接收2個參數(dest interface{}, conds ...interface{})
第1個參數用於接收和保存服務端(mysqld)返回的結果、第2個用於客戶端(MySQL)輸入SQL搜索條件。
什么是內聯條件查詢呢?
就是我們在把接收查詢結果的變量、SQL查詢條件傳遞到.Find()/.First()/.Not()/.Or()這些立即執行函數中執行就屬於內聯條件查詢。
//內聯條件 //1.根據主鍵獲取記錄 (只適用於整形主鍵) u1 := new(User) Model.Debug().First(&u1, 1) fmt.Println(u1.Age) u1 = new(User) Model.Debug().First(&u1, "id = ?", 1) fmt.Println(u1.Name) //2.根據搜索條件獲取多條數據 users := []User{} Model.Debug().Find(&users, "name = ?", "jinzhu") fmt.Println(users) //3.使用結構體作為搜索參數 users = []User{} Model.Debug().Find(&users, User{Age: 29}) fmt.Println(users) //4.使用map組織搜索條件 users = []User{} Model.Debug().Find(&users, map[string]interface{}{"age": "29"}) fmt.Println(users) //5.使用slice作為搜索條件 users=[]User{} Model.Debug().Find(&users,[]int64{1,6}) fmt.Println(users)
設置查詢選項
GORM 提供了 Set
, Get
, InstanceSet
, InstanceGet
方法來允許用戶傳值給 勾子 或其他方法
Gorm 中有一些特性用到了這種機制,如遷移表格時傳遞表格選項。
err = db.Debug().Set("gorm:table_options","ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1").AutoMigrate(&User{}) if err != nil { fmt.Println("創建表失敗",err) } fmt.Println("---------------------","創建表成功")
我們在查詢 SQL是可以設置添加1個行鎖(在我查詢這條記錄的時候別人就無法修改這條數據了)
users=[]User{} Model.Debug().Set("gorm:query_option","FOR UPDATE").Find(&users,[]int64{1,5}) fmt.Println("--------",users) u1=new(User) Model.Set("gorm:query_option","FOR UPDATE").Debug().First(&u1,10) fmt.Println(u1.Name)
FirstOrInit查詢
gorm的核心功能就是把數據庫返回的1條記錄轉換成golang中的結構體並對字段進行賦值。
如果在某些情況下我的SQL查詢沒有返回任何記錄,那我的stuct變量的全部字段就會使用零值。
可以使用 Attrs和Assign方法對 FirstOrInit返回的空記錄 進行struct字段進行賦值。
attrs:獲取匹配的第1條記錄,否則根據給定的條件初始化一個新的對象 (僅支持 struct 和 map 條件)
Assign:不管記錄是否找到,都將參數賦值給 struct變量。
//FirstOrint //如果找到了就把數據庫返回的結果賦值給u2,如果沒有找到也不要緊把查詢條件賦值給u2進行初始化。 u2:=new(User) Model.FirstOrInit(u2,User{Name: "Martin"}) fmt.Println(u2.Name) //在數據庫沒有返回查詢結果的情況下:使用Attrs指定Init時字段的值 u2=new(User) Model.Attrs(User{Age:29} ).FirstOrInit(u2,User{Name: "Bob"}) fmt.Println(u2.Name,u2.Age,u2.ID) //Assigin:不管數據庫有沒有返回查詢結果,在會初始化指定的字段 u2=new(User) Model.Debug().Assign(User{Age: 21}).FirstOrInit(u2,User{Name:"抱牆"}) fmt.Println(u2.Name,u2.Age)
FirstOrCreate插入數據
Attrs和Assign不僅可以對聲明的結構體變量進行初始化,還可以指定FirstOrCreate將要創建的記錄。
FirstOrCreate:獲取匹配的第1條記錄, 否則根據給定的條件創建一個新的記錄 (僅支持 struct 和 map 條件)
Attrs:如果記錄未找到,將使用attr中設置的參數創建 struct 和新記錄.
Assign:不管記錄是否找到,都將參數賦值給 struct 並保存至數據庫.
//FirstOrCreate u3 := new(User) //如果SQL條件匹配到了記錄就返回原有記錄賦值給結構體變量。 Model.Debug().FirstOrCreate(u3, User{Name: "嬴政"}) fmt.Println(u3.Age, u3.ID) //如果SQl查詢沒有匹配到記錄就 insert 1條新記錄,返回新記錄賦值給結構體變量。 u3 = new(User) Model.Debug().Where(User{Name: "嬴胡亥"}).FirstOrCreate(u3) fmt.Println(u3.ID) //Attrs //如果SQL匹配到記錄,返回原記錄並賦值給結構體變量。(不使用Attrs(User{Age: 20})中設置的參數) u3 = new(User) Model.Debug().Where(User{Name: "嬴胡亥"}).Attrs(User{Age: 20}).FirstOrCreate(u3) fmt.Println(u3.Name, u3.Age) //如果SQL沒有匹配到記錄,就使用Where(User{Name: "匹配不到嬴胡亥用戶"})和.Attrs(User{Age: 20})中的參數, insert 1條新記錄,返回新記錄賦值給結構體變量。 u3 = new(User) Model.Debug().Where(User{Name: "匹配不到嬴胡亥用戶"}).Attrs(User{Age: 2000}).FirstOrCreate(u3) fmt.Println(u3.Name, u3.ID) //Assign //SQL匹配到記錄就更新原記錄(update not insert new record) u3 = new(User) Model.Debug().Where(User{Name: "嬴胡亥"}).Attrs(User{Age: 20}).FirstOrCreate(u3) fmt.Println(u3.Name, u3.Age) //沒有匹配到就使用Where(User{Name: "匹配不到嬴胡亥用戶"})和.Attrs(User{Age: 20})中的參數, insert new record。 u3 = new(User) Model.Debug().Where(User{Name: "我不存在"}).Assign(User{Age: 90}).FirstOrCreate(u3) fmt.Println(u3.Name, u3.Age)
子查詢
db.Where("amount > ?", db.Table("orders").Select("AVG(amount)").Where("state = ?", "paid").SubQuery()).Find(&orders) // SELECT * FROM "orders" WHERE "orders"."deleted_at" IS NULL AND (amount > (SELECT AVG(amount) FROM "orders" WHERE (state = 'paid')));
Select選擇字段
Select,指定你想從數據庫中檢索出的字段,默認會選擇全部字段。
//Select,指定你想從數據庫中檢索出的字段,默認會選擇全部字段。 users := []User{} //SELECT id,name FROM `users` Model.Debug().Select("id,name").Find(&users) fmt.Println(users) users = []User{} //SELECT `name`,`age` FROM `users` Model.Debug().Select([]string{"name","age"}).Find(&users) fmt.Println(users) users=[]User{} Model.Debug().Table("users").Select("COALESCE(age,?)", 200) fmt.Println(users)
排序查詢
我們可以通過Order api對查詢的數據進行排序
userList:=[]User{} //SELECT * FROM `users` ORDER BY age desc, name Model.Debug().Order("age desc, name").Find(&userList) fmt.Println(userList) // 多字段排序 userList=[]User{} Model.Debug().Select("id,age").Order("id desc").Order("age").Find(&userList) fmt.Println(userList)
Limit & Offset
分頁查詢:Limit
specify the max number of records to retrieve Offset
specify the number of records to skip before starting to return the records
//limit and offset //返回3條數據 userList:=[]User{} Model.Limit(3).Find(&userList) fmt.Println(userList) // 通過 -1 消除 Limit 條件 Model.Limit(3).Find(&userList).Limit(-1).Find(&userList) fmt.Println(userList) //移動到ID=2的記錄獲取2條,分頁就是這樣搞的! Model.Debug().Offset(2).Limit(2).Find(&userList) fmt.Println(userList)
count獲取查詢總量
Count,該 model 能獲取的記錄總數。Count
必須是鏈式查詢的最后一個操作 ,因為它會覆蓋前面的 SELECT
,但如果里面使用了 count
時不會覆蓋
count1:=new(int64) Model.Debug().Find(&userList).Count(count1) fmt.Println(*count1)
Group&Having
分組和聚合
//group by: SELECT name,sum(age) as total FROM `users` GROUP BY `name` type groupByage struct { Age int Total int } groupList :=[]groupByage{} Model.Debug().Model(&User{}).Select("age,count(name) as total").Group("age").Find(&groupList) fmt.Printf("%#v\n", groupList) //haveing可以對group by 之后的結果,進行進一步篩選。 //SELECT name,sum(age) as total FROM `users` WHERE name LIKE '劉%' GROUP BY `name` HAVING total >= 50 type groupByname struct { Name string Total int } groupList1:=[]groupByname{} Model.Debug().Model(&User{}).Select("name,sum(age) as total").Where("name LIKE ?", "劉%").Group("name").Having("total >= ?",50).Find(&groupList1) fmt.Println(groupList1)
join連表
type result struct { Name string Email string } db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{}) // SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows() for rows.Next() { ... } db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results) // 帶參數的多表連接 db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
Pluck
Query single column from database and scan into a slice, if you want to query multiple columns, use Select
with Scan
instead
Pluck可以幫助我們獲取到table中的某1列數據並賦值給slice。
//pluck獲取age列做位切片返回 var ageList []int64 Model.Debug().Model(&User{}).Pluck("age",&ageList) fmt.Println(ageList) nameList:=new([]string) Model.Debug().Model(&User{}).Pluck("name",&nameList) fmt.Println(nameList)
Scan
Scan results into a struct work similar to Find
Scan的功能類似於Find,可以把從數據庫中查詢到的結果映射到struct中。
//scan的用法 type scanResult struct { Name string Age int64 } //類似於First()的功能 scanResultObj := new(scanResult) Model.Table("users").Select("name,age").Where("name = ?", "嬴政").Scan(scanResultObj) fmt.Println(scanResultObj) //類似於Find()功能 scanResultList := new([]scanResult) Model.Table("users").Select("name,age").Where("name like ?", "劉%").Scan(scanResultList) fmt.Println(scanResultList)
Scope查詢
Scopes allow you to re-use commonly logic, the shared logic needs to defined as type func(*gorm.DB) *gorm.DB
Scopes可以幫助我們把常用的查詢邏輯封裝到1個函數里面,后期使用查詢時Scope這個函數。
//名字不為空 func NameNotNull(db *gorm.DB) *gorm.DB { return db.Where("name is not null") } //查詢年齡>=18歲的用戶 func AgeLe18(db *gorm.DB) *gorm.DB { return db.Where("age >= ?", 18) } //分頁查詢 func PageData(start, count int) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db.Offset(start).Limit(count) } } //scope //查詢姓名不為空 年齡>=18 userList := new([]User) Model.Scopes(NameNotNull, AgeLe18).Find(userList) fmt.Println(userList) //分頁查詢 Model.Scopes(PageData(5,10)).Find(userList) fmt.Println(userList)
鏈式操作相關
Django的Q查詢可以做組合查詢,gorm中的鏈式操作也可以。
var userlist []User contionChain:=Model.Debug().Not("name is null") contionChain.Where("name like ?","劉%") contionChain.Where("age >?",10) contionChain.Find(&userlist) fmt.Println(userlist)
更新 update
Save更新所有字段
var userObj User Model.First(&userObj) fmt.Println(userObj) //save更新: 默認會修改所有字段 userObj.Age=90 //UPDATE `users` SET `id`=1,`created_at`='2020-12-24 05:29:33.978',`updated_at`='2020-12-24 13:35:08.695',`deleted_at`=NULL,`name`='特朗普',`age`=90,`active`=true WHERE `id` = 1 Model.Debug().Save(userObj)
Update更新1個字段
Model(&userObj)我在Model中能傳了1個userObj,gorm會根據userObj中的id字段去數據庫where需要更新的記錄。
//update:更新指定的字段 //根據結構體更新 1個字段 Model.Debug().Model(&userObj).Update("name","唐納德.特朗普") //根據給定的條件更新 1個字段 Model.Debug().Model(&userObj).Where("name = ?","唐納德.特朗普").Update("age",10)
Updates同時更新多個字段
我們可以給update方法傳1個map,同時更新多個字段。
//更新多個字段 使用struct m1:=map[string]interface{}{ "name":"唐納德.特朗普", "age":20, } //UPDATE `users` SET `age`=20,`name`='唐納德.特朗普',`updated_at`='2020-12-24 13:50:07.519' WHERE `id` = 1 //注意:Model(&userObj)我在Model中能傳來1個userObj,gorm會根據userObj中的id字段去where需要更新的值 Model.Debug().Model(&userObj).Updates(m1)
Updates更新指定的字段
有時我們接收到的map可能會包含一些我們不需要更新的字段,那么如何指定有效字段、排除無效字段呢?
//updates:更新指定字段 //指定age字段進行更新 Model.Debug().Model(&userObj).Select("age").Updates(map[string]interface{}{"name":"唐納德.-特朗普","age":9000,"active":false}) //排除age字段進行更新 Model.Debug().Model(&userObj).Omit("age").Updates(map[string]interface{}{"name":"唐納德.-特朗普","age":9000,"active":false})
UpdateColumns
UpdateColumns也叫無Hooks更新。
上面的更新操作會自動運行 model 的 BeforeUpdate
, AfterUpdate
方法。
更新 UpdatedAt
時間戳, 在更新時保存其 Associations
, 如果你不想調用這些方法,你可以使用 UpdateColumn
, UpdateColumns就不會更新
UpdatedAt字段。
//UpdateColumn Model.Debug().Model(&userObj).Update("name", "唐納德.特朗普") Model.Debug().Model(&userObj).UpdateColumns(map[string]interface{}{ "name": "唐納德.-特朗普", "age": 200, }) Model.Debug().Model(&userObj).UpdateColumns(User{Name:"唐納德.特朗普",Active: true}) defer connectionPool.Close()
批量更新多條記錄
注意在批量更新的時候不會更新 UpdatedAt
字段。
//批量更新 AffectedRows:=Model.Debug().Table("users").Where("id in (?)", []int{1, 2}).Updates(map[string]interface{}{ "age": 21, }).RowsAffected fmt.Println("更新的行數",AffectedRows)
全表更新
我想要1張表中的某個字段全部更新怎么辦?還記得Django中的F在原數據的基礎上+1?
//全局更新 //錯誤的全局更新方式,之前是可以的!! err := Model.Model(&User{}).Update("name", "二狗").Error // 錯誤的寫法!!gorm.ErrMissingWhereClause fmt.Println(err) //全局更新方式1 Model.Debug().Exec("UPDATE users SET age = ?", "18") //讓user表中用戶年齡在原來的基礎上+2,Expr 子查詢 Model.Debug().Session(&gorm.Session{AllowGlobalUpdate: true}).Model(User{}).Update("age", gorm.Expr("age+ ?", 100))
刪除delete
最后就是刪除了~
刪除1條記錄
我跟可以根據對象刪除一條記錄,注意必須指定對象的主鍵否則會觸發 批量 Delete,也可以根據主鍵直接刪除1條記錄。
//根據對象刪除:刪除對象需要指定主鍵,否則會觸發 批量 Delete u:=new(User) u.ID=1 Model.Debug().Delete(u) //根據主鍵刪除 Model.Debug().Delete(&User{},10) //根據主鍵刪除多個 Model.Debug().Delete(&User{},[]int{4,11})
批量刪除
gorm中都是軟刪除。
//2.批量刪除 deletedUsers:=[]User{} Model.Debug().Where("name like ?","唐納德%").Delete(User{}) Model.Debug().Delete(deletedUsers,"name like ?","嬴%") fmt.Println(deletedUsers)
全表刪除
清空表中的內容
//全局刪除 //1.錯誤的方式 err:=Model.Delete(&User{}).Error fmt.Println(err) //正確方式 Model.Where("1 = 1").Delete(&User{}) Model.Exec("truncate table users") Model.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
表關聯
在關系型數據里表和表之間的關系:1對1(主鍵+外鍵聯合唯一)、1對多(外鍵)、多對多(中間增加1張記錄2個之間相互1對多關系的表),外鍵總在多的一方設置。
平時我們討論表關系時有人會拋出 belongs to(屬於)、多對1、1對多這些概念,此時你千萬別懵逼。其實反映到數據庫里還是外鍵。
主表:在數據庫中建立的表格即Table,其中存在主鍵(primary key)用於與其它表相關聯,並且作為在主表中的唯一性標識。
從表:以主表的主鍵(primary key)值為外鍵 (Foreign Key)的表;從表可以通過外鍵與主表進行關聯查詢,從表通過外鍵於主表進行關聯查詢
可能你理解的主和從表概念和綜上所述是相反的,沒關系這不影響技術愛好者之間的學習、交流,本文采用百度百科的定義的概念:外鍵在哪個表,那個表就是從表。
以上2張表存在外鍵關聯:
一對多:從球隊角度來說一個球隊擁有多個球員 即為一對多
多對一:從球員角度來說多個球員屬於一個球隊 即為多對一數據表間一對多關系如下圖:
無論是1對多,還是多對1,本質在數據庫里的體現還是1個外鍵。
外鍵連表查詢
以上我們上面大量篇幅說主表從表,其實就是2個表存在外鍵關系。
根據自身業務需求設計外鍵就好了,我一般稱在從表使用外鍵查詢就屬於正向連表,否則就是反向連表查詢。
一個外鍵引發了太多沒有意義的思考,好吧開始定義model和外鍵吧。
使用默認外鍵字段名稱
type Role struct { gorm.Model Name string `gorm:"not null"` } type User struct { gorm.Model Name string `gorm:"not null"` //RoleID:不是可以隨便寫的,指定User表和Role表名的ID是字段建立外鍵。 RoleID string `gorm:"not null"` Role Role }
自定義外鍵名稱
以上我們通過默認的外鍵:使用擁有者(Role)的struct名+上主字段名做外鍵關聯到了主表。也可以在從表自定義外鍵的名稱。
type Role struct { gorm.Model Name string `gorm:"not null"` } type User struct { gorm.Model Name string `gorm:"not null"` //外鍵字段名稱叫role_id不好聽,我要叫role_refer RoleRefer string `gorm:"not null"` Role Role `gorm:"foreignKey:RoleRefer"` }
自定義外鍵字段
我從表一定要外鍵到主表ID這個列?我想外鍵到其他列!
type Role struct { gorm.Model Name string `gorm:"not null"` } type User struct { gorm.Model Name string `gorm:"not null"` //從表外鍵字段名稱叫role_id不好聽,我要叫role_refer RoleRefer string `gorm:"not null"` //我想要外鍵到主表的Name字段 Role Role `gorm:"foreignKey:RoleRefer;AssociationForeignKey:Name"` }
最終定義1個模型
type Role struct { gorm.Model Name string `gorm:"not null"` //反向連表查詢用到Association(Users) Users []User } type User struct { gorm.Model Name string `gorm:"not null"` //RoleID:與Role表名的ID是字段建立外鍵。正向連表查詢 RoleID string `gorm:"not null"` Role Role }
正向和反向連表查詢
//查詢id=1的用戶是什么角色? var user1 User //正向連表Preload Model.Preload("Role","id is not null").First(&user1,"id =?",2) fmt.Println("---",user1.Role) //在主表查詢id=2的角色被多少用戶使用? var role Role Model.First(&role,"id=?",2) fmt.Println(role.Name) //反向連表 Model.Model(&role).Association("Users").Find(&role.Users) fmt.Println(role.Users)
多對多查詢
我現在想把用戶和角色變成多對多的關系。1個用戶可以有多個角色,1個角色也可以被多個用戶使用。這就不需要外鍵了,現在得使用1張中間關系表。
type Role struct { gorm.Model Name string `gorm:"not null"` Users []User `gorm:"many2many:user_roles;"` }
type User struct { gorm.Model Name string `gorm:"not null"` Roles []Role `gorm:"many2many:user_roles;"` }
多對多查詢最簡單了
var user1 User Model.Where("id = ?", "1").First(&user1) //查詢一下當前用戶都有那些角色 Model.Model(&user1).Association("Roles").Find(&user1.Roles) fmt.Println(user1.Roles) var role1 Role Model.Where("id = ?", "2").First(&role1) //查詢一下當前角色都有那些用戶在使用 Model.Model(&role1).Association("Users").Find(&role1.Users) fmt.Println(role1.Users)