Go Gorm 使用

簡介

官方文檔: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)
}

image-20230207172833647

最開始數據庫不存在,也有錯誤提示,按提示修改即可

model

官方model 更多用法:https://gorm.io/zh_CN/docs/models.html#embedded_struct

GO 操作數據庫是需要將model 模型創建出來的,並不想think-php 不用寫,自動映射,我這裏例子還是字段比較少的,如果字段多了,這也太費時間了,所以推薦一個自動生成的

http://sql2struct.atotoa.com/

可以直接將建表語句轉爲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 作爲主鍵,使用結構體名的 蛇形複數 作爲表名,字段名的 蛇形 作爲列名,並使用 CreatedAtUpdatedAt 字段追蹤創建、更新時間

如果您遵循 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)

}

輸出image-20230207185712967

數據庫

image-20230207185832834

刪除

官方: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 方法支持 structmap[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 指定要更新的字段

更新選定字段

如果您想要在更新時選定、忽略某些字段,您可以使用 SelectOmit

// 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 提供了 FirstTakeLast 方法,以便從數據庫中檢索單個對象。當查詢數據庫時它添加了 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 object db.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 as id to nil before you want to use the variable such as user 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章