# 鏈式方法
GORM 允許進行鏈式操作,所以您可以像這樣寫代碼:
```go
db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
```
GORM 中有三種類型的方法: `鏈式方法`、`Finisher 方法`、`新建會話方法`
## 鏈式方法
鏈式方法是將 `Clauses` 修改或添加到當前 `Statement` 的方法,例如:
`Where`, `Select`, `Omit`, `Joins`, `Scopes`, `Preload`, `Raw`…
這是 [完整方法列表](https://github.com/go-gorm/gorm/blob/master/chainable_api.go),也可以查看 [SQL 構建器](http://v2.gorm.io/zh_CN/docs/sql_builder.html) 獲取更多關于 `Clauses` 的信息
## Finisher Method
Finishers 是會立即執行注冊回調的方法,然后生成并執行 SQL,比如這些方法:
`Create`, `First`, `Find`, `Take`, `Save`, `Update`, `Delete`, `Scan`, `Row`, `Rows`…
查看[完整方法列表](https://github.com/go-gorm/gorm/blob/master/finisher_api.go)
## 新建會話模式
在初始化了 `*gorm.DB` 或 `新建會話方法` 后, 調用下面的方法會創建一個新的 `Statement` 實例而不是使用當前的
GROM 定義了 `Session`、`WithContext`、`Debug` 方法做為 `新建會話方法`,參考[會話](http://v2.gorm.io/zh_CN/docs/session.html) 獲得詳細
讓我們用一些例子來解釋它:
示例 1:
```go
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db 是一個剛完成初始化的 *gorm.DB 實例,這是一個 `新建會話`
db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
// `Where("name = ?", "jinzhu")` 是調用的第一個方法,它會創建一個新 `Statement`
// `Where("age = ?", 18)` 會復用 `Statement`,并將條件添加至這個 `Statement`
// `Find(&users)` 是一個 finisher 方法,它運行注冊的查詢回調,生成并運行下面這條 SQL:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18;
db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users)
// `Where("name = ?", "jinzhu2")` 也是調用的第一個方法,也會創建一個新 `Statement`
// `Where("age = ?", 20)` 會復用 `Statement`,并將條件添加至這個 `Statement`
// `Find(&users)` 是一個 finisher 方法,它運行注冊的查詢回調,生成并運行下面這條 SQL:
// SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20;
db.Find(&users)
// 對于這個 `新建會話模式` 的 `*gorm.DB` 實例來說,`Find(&users)` 是一個 finisher 方法也是第一個調用的方法。
// 它創建了一個新的 `Statement` 運行注冊的查詢回調,生成并運行下面這條 SQL:
// SELECT * FROM users;
```
示例 2:
```go
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db 是一個剛完成初始化的 *gorm.DB 實例,這是一個 `新建會話`
tx := db.Where("name = ?", "jinzhu")
// `Where("name = ?", "jinzhu")` 是第一個被調用的方法,它創建了一個新的 `Statement` 并添加條件
tx.Where("age = ?", 18).Find(&users)
// `tx.Where("age = ?", 18)` 會復用上面的那個 `Statement`,并向其添加條件
// `Find(&users)` 是一個 finisher 方法,它運行注冊的查詢回調,生成并運行下面這條 SQL:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18
tx.Where("age = ?", 28).Find(&users)
// `tx.Where("age = ?", 18)` 同樣會復用上面的那個 `Statement`,并向其添加條件
// `Find(&users)` 是一個 finisher 方法,它運行注冊的查詢回調,生成并運行下面這條 SQL:
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 20;
```
**注意** 在示例 2 中,第一個查詢會影響第二個查詢生成的 SQL ,因為 GORM 復用 `Statement` 這可能會引發預期之外的問題,請參考 [線程安全](http://v2.gorm.io/zh_CN/docs/method_chaining.html#goroutine_safe) 了解如何避免該問題。
## 線程安全
新初始化的 `*gorm.DB` 或調用 `新建會話方法` 后,GORM 會創建新的 `Statement` 實例。因此想要復用 `*gorm.DB`,您需要確保它們處于 `新建會話模式`,例如:
```go
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 安全的使用新初始化的 *gorm.DB
for i := 0; i < 100; i++ {
go db.Where(...).First(&user)
}
tx := db.Where("name = ?", "jinzhu")
// 不安全的復用 Statement
for i := 0; i < 100; i++ {
go tx.Where(...).First(&user)
}
ctx, _ := context.WithTimeout(context.Background(), time.Second)
ctxDB := db.WithContext(ctx)
// 在 `新建會話方法` 之后是安全的
for i := 0; i < 100; i++ {
go ctxDB.Where(...).First(&user)
}
ctx, _ := context.WithTimeout(context.Background(), time.Second)
ctxDB := db.Where("name = ?", "jinzhu").WithContext(ctx)
// 在 `新建會話方法` 之后是安全的
for i := 0; i < 100; i++ {
go ctxDB.Where(...).First(&user) // `name = 'jinzhu'` 會應用到每次循環中
}
tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{WithConditions: true})
// 在 `新建會話方法` 之后是安全的
for i := 0; i < 100; i++ {
go tx.Where(...).First(&user) // `name = 'jinzhu'` 會應用到每次循環中
}
```