簡介
官方文檔:https://gorm.io/zh_CN/docs/index.html
github :https://github.com/go-gorm/gorm
同其他語言的ORM框架一樣,這是Go 版本的
本文全部以mysql
爲例
sql 腳本
爲了方便測試,這裏準備一份sql 腳本,來創建數據庫和表
CREATE DATABASE makalo_test;
USE makalo_test;
CREATE TABLE `test` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`user_name` varchar(50) DEFAULT '' COMMENT '用戶名',
`password` varchar(50) DEFAULT '' COMMENT '密碼',
`description` varchar(500) DEFAULT '' COMMENT '描述',
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`delete_at` timestamp NULL DEFAULT NULL COMMENT '刪除時間',
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='makalo 測試表';
連接mysql 數據庫
官方
https://gorm.io/zh_CN/docs/connecting_to_the_database.html
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
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{})
}
可以看到上面引入兩個庫
- "gorm.io/driver/mysql"
- "gorm.io/gorm"
那我們將這兩個庫下載並添加進去
go get gorm.io/gorm
go get gorm.io/driver/mysql
連接本地mysql 數據庫示例
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
// dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
dsn := "root:root@tcp(127.0.0.1:3306)/makalo_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Printf("db => %+v, err => %+v", db, err)
}
最開始數據庫不存在,也有錯誤提示,按提示修改即可
model
官方model 更多用法:https://gorm.io/zh_CN/docs/models.html#embedded_struct
GO 操作數據庫是需要將model 模型創建出來的,並不想think-php 不用寫,自動映射,我這裏例子還是字段比較少的,如果字段多了,這也太費時間了,所以推薦一個自動生成的
可以直接將建表語句轉爲GO 的model 結構體,那我們這張表的model如下:
可以看到連Tag 都寫好了 太貼心了
// makalo 測試表
type Test struct {
Id uint64 `gorm:"column:id" db:"id" json:"id" form:"id"` //自增id
UserName string `gorm:"column:user_name" db:"user_name" json:"user_name" form:"user_name"` //用戶名
Password string `gorm:"column:password" db:"password" json:"password" form:"password"` //密碼
Description string `gorm:"column:description" db:"description" json:"description" form:"description"` //描述
CreateAt int64 `gorm:"column:create_at" db:"create_at" json:"create_at" form:"create_at"` //創建時間
DeleteAt int64 `gorm:"column:delete_at" db:"delete_at" json:"delete_at" form:"delete_at"` //刪除時間
UpdateAt int64 `gorm:"column:update_at" db:"update_at" json:"update_at" form:"update_at"` //更新時間
}
但是需要注意的是gorm.Model 的約定
GORM 傾向於約定優於配置 默認情況下,GORM 使用
ID
作爲主鍵,使用結構體名的蛇形複數
作爲表名,字段名的蛇形
作爲列名,並使用CreatedAt
、UpdatedAt
字段追蹤創建、更新時間如果您遵循 GORM 的約定,您就可以少寫的配置、代碼。 如果約定不符合您的實際要求,GORM 允許你配置它們
但是因爲跟他默認約定有點不一樣,所以還是要改點配置的,改完後
// makalo 測試表
type Test struct {
// 指定 主鍵 並 指定數據庫列名
Id uint64 `gorm:"primaryKey;column:id" db:"id" json:"id" form:"id"` //自增id
UserName string `gorm:"column:user_name" db:"user_name" json:"user_name" form:"user_name"` //用戶名
Password string `gorm:"column:password" db:"password" json:"password" form:"password"` //密碼
Description string `gorm:"column:description" db:"description" json:"description" form:"description"` //描述
CreateAt time.Time `gorm:"column:create_at" db:"create_at" json:"create_at" form:"create_at"` //創建時間
DeleteAt gorm.DeletedAt `gorm:"index;column:delete_at" db:"delete_at" json:"delete_at" form:"delete_at"` //刪除時間
UpdateAt time.Time `gorm:"column:update_at" db:"update_at" json:"updated_at" form:"update_at"` //更新時間
}
表名設置
GORM 使用結構體名的 蛇形命名
作爲表名。對於結構體 User
,根據約定,其表名爲 users
也就是說 GORM 會默認將你的 model 結構體 轉小寫 並加上 複數 s
TableName
您可以實現 Tabler
接口來更改默認表名,例如:
type Tabler interface {
TableName() string
}
// TableName 會將 User 的表名重寫爲 `profiles`
func (User) TableName() string {
return "profiles"
}
注意:
TableName
不支持動態變化,它會被緩存下來以便後續使用。想要使用動態表名,你可以使用Scopes
,例如:
func UserTable(user User) func (tx *gorm.DB) *gorm.DB {
return func (tx *gorm.DB) *gorm.DB {
if user.Admin {
return tx.Table("admin_users")
}
return tx.Table("users")
}
}
db.Scopes(UserTable(user)).Create(&user)
示例
只需實現 TableName() 接口即可
// TableName 會將 tests 的表名重寫爲 `test`
func (Test) TableName() string {
return "test"
}
臨時指定表名
您可以使用 Table
方法臨時指定表名,例如:
// 根據 User 的字段創建 `deleted_users` 表
db.Table("deleted_users").AutoMigrate(&User{})
// 從另一張表查詢數據
var deletedUsers []User
db.Table("deleted_users").Find(&deletedUsers)
// SELECT * FROM deleted_users;
db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
// DELETE FROM deleted_users WHERE name = 'jinzhu';
查看 from 子查詢 瞭解如何在 FROM 子句中使用子查詢
插入
官方文檔:
https://gorm.io/zh_CN/docs/create.html
我的示例
package main
import (
"fmt"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// makalo 測試表
type Test struct {
// 指定 主鍵 並 指定數據庫列名
Id uint64 `gorm:"primaryKey" gorm:"column:id" db:"id" json:"id" form:"id"` //自增id
UserName string `gorm:"column:user_name" db:"user_name" json:"user_name" form:"user_name"` //用戶名
Password string `gorm:"column:password" db:"password" json:"password" form:"password"` //密碼
Description string `gorm:"column:description" db:"description" json:"description" form:"description"` //描述
CreateAt time.Time `gorm:"column:create_at" db:"create_at" json:"create_at" form:"create_at"` //創建時間
DeleteAt gorm.DeletedAt `gorm:"index gorm:"column:delete_at" db:"delete_at" json:"delete_at" form:"delete_at"` //刪除時間
UpdateAt time.Time `gorm:"column:update_at" db:"update_at" json:"updated_at" form:"update_at"` //更新時間
}
// TableName 會將 tests 的表名重寫爲 `test`
func (Test) TableName() string {
return "test"
}
func main() {
// 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
// dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
dsn := "root:root@tcp(127.0.0.1:3306)/makalo_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Printf("db => %+v, err => %+v \n ", db, err)
test := Test{
UserName: "makalo",
Password: "password",
Description: "賊雞兒帥",
}
// 通過數據的指針來插入
// 插入所有字段
// result := db.Create(&test)
// 只插入給出的字段
result := db.Select("UserName", "Password", "Description").Create(&test)
// test.Id // 返回插入數據的主鍵
// result.Error // 返回 error
// result.RowsAffected // 返回插入記錄的條數
fmt.Printf("id => %d, error => %+v, RowsAffected => %+v", test.Id, result.Error, result.RowsAffected)
}
輸出
數據庫
刪除
官方:https://gorm.io/zh_CN/docs/delete.html
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;
db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);
// 帶額外條件的刪除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";
示例
// 刪除
db.Delete(&Test{}, 1)
軟刪除
如果您的模型包含了一個 gorm.deletedat
字段(gorm.Model
已經包含了該字段),它將自動獲得軟刪除的能力!
擁有軟刪除能力的模型調用 Delete
時,記錄不會從數據庫中被真正刪除。但 GORM 會將 DeletedAt
置爲當前時間, 並且你不能再通過普通的查詢方法找到該記錄。
// user 的 ID 是 `111`
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// 批量刪除
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// 在查詢時會忽略被軟刪除的記錄
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
如果您不想引入 gorm.Model
,您也可以這樣啓用軟刪除特性:
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
查找被軟刪除的記錄
您可以使用 Unscoped
找到被軟刪除的記錄
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
永久刪除
您也可以使用 Unscoped
永久刪除匹配的記錄
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;
修改
官方:https://gorm.io/zh_CN/docs/update.html
保存所有字段
Save
會保存所有的字段,即使字段是零值
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
更新單列時,需要有一些條件,否則將會引起錯誤 ErrMissingWhereClause
,查看 阻止全局更新 瞭解詳情。 當使用 Model
方法,並且值中有主鍵值時,主鍵將會被用於構建條件,例如:
// 條件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
// 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;
// 根據條件和 model 的值進行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
更新多列
Updates
方法支持 struct
和 map[string]interface{}
參數。當使用 struct
更新時,默認情況下,GORM 只會更新非零值的字段
// 根據 `struct` 更新屬性,只會更新非零值的字段
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;
// 根據 `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;
注意 當使用 struct 進行更新時,GORM 只會更新非零值的字段。 你可以使用
map
更新字段,或者使用Select
指定要更新的字段
更新選定字段
如果您想要在更新時選定、忽略某些字段,您可以使用 Select
、Omit
// Select with Map
// User's ID is `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;
// Select with Struct (select zero value fields)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// Select all fields (select all fields include zero value fields)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
// Select all fields but omit Role (select all fields include zero value fields)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
增刪改的完整代碼
package main
import (
"fmt"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// makalo 測試表
type Test struct {
// 指定 主鍵 並 指定數據庫列名
Id uint64 `gorm:"primaryKey;column:id" db:"id" json:"id" form:"id"` //自增id
UserName string `gorm:"column:user_name" db:"user_name" json:"user_name" form:"user_name"` //用戶名
Password string `gorm:"column:password" db:"password" json:"password" form:"password"` //密碼
Description string `gorm:"column:description" db:"description" json:"description" form:"description"` //描述
CreateAt time.Time `gorm:"column:create_at" db:"create_at" json:"create_at" form:"create_at"` //創建時間
DeleteAt gorm.DeletedAt `gorm:"index;column:delete_at" db:"delete_at" json:"delete_at" form:"delete_at"` //刪除時間
UpdateAt time.Time `gorm:"column:update_at" db:"update_at" json:"updated_at" form:"update_at"` //更新時間
}
// TableName 會將 tests 的表名重寫爲 `test`
func (Test) TableName() string {
return "test"
}
func main() {
// 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
// dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
dsn := "root:root@tcp(127.0.0.1:3306)/makalo_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Printf("db => %+v, err => %+v \n ", db, err)
/* test := Test{
UserName: "makalo",
Password: "password",
Description: "賊雞兒帥",
} */
// 通過數據的指針來插入
// 插入所有字段
// result := db.Create(&test)
// 只插入給出的字段
// result := db.Select("UserName", "Password", "Description").Create(&test)
// test.Id // 返回插入數據的主鍵
// result.Error // 返回 error
// result.RowsAffected // 返回插入記錄的條數
// fmt.Printf("id => %d, error => %+v, RowsAffected => %+v", test.Id, result.Error, result.RowsAffected)
// 刪除
// 因爲配置 DeleteAt 字段所以會被軟刪除
// db.Delete(&Test{}, 1)
// 查找被軟刪除的記錄
// result := db.Unscoped().Where("id = 1").Find(&test)
// fmt.Printf("test => %d, result => %+v", test, result)
// 永久刪除
// db.Unscoped().Delete(&test)
// 更新
// db.Model(&Test{}).Where("user_name", "makalo").Update("description", "猛男")
}
查詢
查詢太多了,我把官網的copy 過來了,可以自己試下,感覺都有用
官網:https://gorm.io/zh_CN/docs/query.html
檢索單個對象
GORM 提供了 First
、Take
、Last
方法,以便從數據庫中檢索單個對象。當查詢數據庫時它添加了 LIMIT 1
條件,且沒有找到記錄時,它會返回 ErrRecordNotFound
錯誤
// 獲取第一條記錄(主鍵升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 獲取一條記錄,沒有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 獲取最後一條記錄(主鍵降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的記錄數
result.Error // returns error or nil
// 檢查 ErrRecordNotFound 錯誤
errors.Is(result.Error, gorm.ErrRecordNotFound)
如果你想避免
ErrRecordNotFound
錯誤,你可以使用Find
,比如db.Limit(1).Find(&user)
,Find
方法可以接受struct和slice的數據。
Using
Find
without a limit for single objectdb.Find(&user)
will query the full table and return only the first object which is not performant and nondeterministic
The First
and Last
methods will find the first and last record (respectively) as ordered by primary key. They only work when a pointer to the destination struct is passed to the methods as argument or when the model is specified using db.Model()
. Additionally, if no primary key is defined for relevant model, then the model will be ordered by the first field. For example:
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 string
Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
根據主鍵檢索
Objects can be retrieved using primary key by using Inline Conditions if the primary key is a number. When working with strings, extra care needs to be taken to avoid SQL Injection; check out Security section for details.
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);
If the primary key is a string (for example, like a uuid), the query will be written as follows:
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
當目標對象有一個主鍵值時,將使用主鍵構建查詢條件,例如:
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;
檢索全部對象
// Get all records
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // returns found records count, equals `len(users)`
result.Error // returns error
條件
String 條件
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
If the object’s primary key has been set, then condition query wouldn’t cover the value of primary key but use it as a ‘and’ condition. For example:
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
This query would give
record not found
Error. So set the primary key attribute such asid
to nil before you want to use the variable such asuser
to get new value from database.
Struct & Map 條件
// 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);
NOTE When querying with struct, GORM will only query with non-zero fields, that means if your field’s value is
0
,''
,false
or other zero values, it won’t be used to build query conditions, for example:
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";
To include zero values in the query conditions, you can use a map, which will include all key-values as query conditions, for example:
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
For more details, see Specify Struct search fields.
指定結構體查詢字段
When searching with struct, you can specify which particular values from the struct to use in the query conditions by passing in the relevant field name or the dbname to Where()
, for example:
db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;
內聯條件
Query conditions can be inlined into methods like First
and Find
in a similar way to Where
.
// Get by primary key if it were a non-integer type
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';
// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";
db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
// Struct
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;
// Map
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;
Not 條件
Build NOT conditions, works similar to Where
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
Or 條件
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
For more complicated SQL queries. please also refer to Group Conditions in Advanced Query.
選擇特定字段
Select
allows you to specify the fields that you want to retrieve from database. Otherwise, GORM will select all fields by default.
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;
db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;
Also check out Smart Select Fields
排序
Specify order when retrieving records from the database
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
db.Clauses(clause.OrderBy{
Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
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
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
Refer to Pagination for details on how to make a paginator
Group By & Having
type result struct {
Date time.Time
Total int
}
db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name` LIMIT 1
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
defer rows.Close()
for rows.Next() {
...
}
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
defer rows.Close()
for rows.Next() {
...
}
type Result struct {
Date time.Time
Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
Distinct
Selecting distinct values from the model
db.Distinct("name", "age").Order("name, age desc").Find(&results)
Distinct
works with Pluck
and Count
too
Joins
Specify Joins conditions
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)
// multiple joins with parameter
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "[email protected]").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
Joins 預加載
You can use Joins
eager loading associations with a single SQL, for example:
db.Joins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
Join with conditions
db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` AND `Company`.`alive` = true;
For more details, please refer to Preloading (Eager Loading).
Joins 一個衍生表
You can also use Joins
to join a derived table.
type User struct {
Id int
Age int
}
type Order struct {
UserId int
FinishedAt *time.Time
}
query := db.Table("order").Select("MAX(order.finished_at) as latest").Joins("left join user user on order.user_id = user.id").Where("user.age > ?", 18).Group("order.user_id")
db.Model(&Order{}).Joins("join (?) q on order.finished_at = q.latest", query).Scan(&results)
// SELECT `order`.`user_id`,`order`.`finished_at` FROM `order` join (SELECT MAX(order.finished_at) as latest FROM `order` left join user user on order.user_id = user.id WHERE user.age > 18 GROUP BY `order`.`user_id`) q on order.finished_at = q.latest
Scan
Scanning results into a struct works similarly to the way we use Find
type Result struct {
Name string
Age int
}
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
// Raw SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
原生SQL
官網:https://gorm.io/zh_CN/docs/sql_builder.html
原生查詢 SQL 和 Scan
type Result struct {
ID int
Name string
Age int
}
var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
var age int
db.Raw("SELECT SUM(age) FROM users WHERE role = ?", "admin").Scan(&age)
var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&users)
Exec
原生 SQL
db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
// Exec with SQL Expression
db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
SQL 生成器
官網:https://gorm.io/zh_CN/docs/sql_builder.html#DryRun-模式
DryRun 模式
在不執行的情況下生成 SQL
及其參數,可以用於準備或測試生成的 SQL,詳情請參考 Session
stmt := db.Session(&Session{DryRun: true}).First(&user, 1).Statement
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
stmt.Vars //=> []interface{}{1}
ToSQL
返回生成的 SQL
但不執行。
GORM使用 database/sql 的參數佔位符來構建 SQL 語句,它會自動轉義參數以避免 SQL 注入,但我們不保證生成 SQL 的安全,請只用於調試。
sql := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
})
sql //=> SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10
Row
& Rows
獲取 *sql.Row
結果
// 使用 GORM API 構建 SQL
row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)
// 使用原生 SQL
row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
row.Scan(&name, &age, &email)
獲取 *sql.Rows
結果
// 使用 GORM API 構建 SQL
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows()
defer rows.Close()
for rows.Next() {
rows.Scan(&name, &age, &email)
// 業務邏輯...
}
// 原生 SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows()
defer rows.Close()
for rows.Next() {
rows.Scan(&name, &age, &email)
// 業務邏輯...
}
轉到 FindInBatches 獲取如何在批量中查詢和處理記錄的信息, 轉到 Group 條件 獲取如何構建複雜 SQL 查詢的信息
將 sql.Rows
掃描至 model
使用 ScanRows
將一行記錄掃描至 struct,例如:
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
var user User
for rows.Next() {
// ScanRows 將一行掃描至 user
db.ScanRows(rows, &user)
// 業務邏輯...
}
連接
在一條 tcp DB 連接中運行多條 SQL (不是事務)
db.Connection(func(tx *gorm.DB) error {
tx.Exec("SET my.role = ?", "admin")
tx.First(&User{})
})
高級
子句(Clause)
GORM 內部使用 SQL builder 生成 SQL。對於每個操作,GORM 都會創建一個 *gorm.Statement
對象,所有的 GORM API 都是在爲 statement
添加、修改 子句
,最後,GORM 會根據這些子句生成 SQL
例如,當通過 First
進行查詢時,它會在 Statement
中添加以下子句
clause.Select{Columns: "*"}
clause.From{Tables: clause.CurrentTable}
clause.Limit{Limit: 1}
clause.OrderByColumn{
Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
}
然後 GORM 在 Query
callback 中構建最終的查詢 SQL,像這樣:
Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR")
生成 SQL:
SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
您可以自定義 子句
並與 GORM 一起使用,這需要實現 Interface 接口
可以參考 示例
子句構造器
不同的數據庫, 子句可能會生成不同的 SQL,例如:
db.Offset(10).Limit(5).Find(&users)
// SQL Server 會生成
// SELECT * FROM "users" OFFSET 10 ROW FETCH NEXT 5 ROWS ONLY
// MySQL 會生成
// SELECT * FROM `users` LIMIT 5 OFFSET 10
之所以支持 Clause,是因爲 GORM 允許數據庫驅動程序通過註冊 Clause Builder 來取代默認值,這兒有一個 Limit 的示例
子句選項
GORM 定義了很多 子句,其中一些 子句提供了你可能會用到的選項
儘管很少會用到它們,但如果你發現 GORM API 與你的預期不符合。這可能可以很好地檢查它們,例如:
db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&user)
// INSERT IGNORE INTO users (name,age...) VALUES ("jinzhu",18...);
StatementModifier
GORM 提供了 StatementModifier 接口,允許您修改語句,使其符合您的要求,這兒有一個 Hint 示例
import "gorm.io/hints"
db.Clauses(hints.New("hint")).Find(&User{})
// SELECT * /*+ hint */ FROM `users