Method Chaining

GORMはメソッドチェーンが可能なため、次のようなコードを書くことができます。

db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)

GORMには Chain Method, Finisher Method, New Session Methodという3種類のメソッドがあります:

Chain Method

Chain Methodsは現在のStatementClausesを変更または追加するメソッドです。

Where, Select, Omit, Joins, Scopes, Preload, Raw

こちらがChain Methodの一覧です。Clausesについての詳細は SQL Builderを参照してください。

Finisher Method

Finishersは登録されたコールバックを実行する即時メソッドで、SQLを生成して実行します。

Create, First, Find, Take, Save, Update, Delete, Scan, Row, Rows

Finisher Methodの一覧 を参照してください。

New Session Mode

*gorm.DBが新しく初期化されたか、New Session Methodが実行された後、 次のメソッド呼び出しは、現在のインスタンスを使用する代わりに新しいStatementインスタンスを作成します。

GROMはSession, WithContext, DebugのメソッドをNew Session Methodとして定義しています。詳細については Sessionを参照してください。

以下の例で説明しましょう。

例1:

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is new initialized *gorm.DB, which under `New Session Mode`
db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
// `Where("name = ?", "jinzhu")` is the first method call, it will creates a new `Statement`
// `Where("age = ?", 18)` reuse the `Statement`, and add conditions to the `Statement`
// `Find(&users)` is a finisher, it executes registered Query Callbacks, generate and run following SQL
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18;

db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users)
// `Where("name = ?", "jinzhu2")` is also the first method call, it creates new `Statement` too
// `Where("age = ?", 20)` reuse the `Statement`, and add conditions to the `Statement`
// `Find(&users)` is a finisher, it executes registered Query Callbacks, generate and run following SQL
// SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20;

db.Find(&users)
// `Find(&users)` is a finisher method and also the first method call for a `New Session Mode` `*gorm.DB`
// It creates a new `Statement` and executes registered Query Callbacks, generates and run following SQL
// SELECT * FROM users;

例:2

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// db is new initialized *gorm.DB, which under `New Session Mode`
tx := db.Where("name = ?", "jinzhu")
// `Where("name = ?", "jinzhu")` is the first method call, it creates a new `Statement` and add conditions

tx.Where("age = ?", 18).Find(&users)
// `tx.Where("age = ?", 18)` REUSE above `Statement`, and add conditions to the `Statement`
// `Find(&users)` is a finisher, it executes registered Query Callbacks, generate and run following SQL
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18

tx.Where("age = ?", 28).Find(&users)
// `tx.Where("age = ?", 18)` REUSE above `Statement` also, and add conditions to the `Statement`
// `Find(&users)` is a finisher, it executes registered Query Callbacks, generate and run following SQL
// SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 20;

NOTE In example 2, the first query affected the second generated SQL as GORM reused the Statement, this might cause unexpected issues, refer Goroutine Safety for how to avoid it

Method Chain Safety/Goroutine Safety

Methods will create new Statement instances for new initialized *gorm.DB or after a New Session Method, so to reuse a *gorm.DB, you need to make sure they are under New Session Mode, for example:

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

// Safe for new initialized *gorm.DB
for i := 0; i < 100; i++ {
go db.Where(...).First(&user)
}

tx := db.Where("name = ?", "jinzhu")
// NOT Safe as reusing Statement
for i := 0; i < 100; i++ {
go tx.Where(...).First(&user)
}

ctx, _ := context.WithTimeout(context.Background(), time.Second)
ctxDB := db.WithContext(ctx)
// Safe after a `New Session Method`
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)
// Safe after a `New Session Method`
for i := 0; i < 100; i++ {
go ctxDB.Where(...).First(&user) // `name = 'jinzhu'` will apply to the query
}

tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{WithConditions: true})
// Safe after a `New Session Method`
for i := 0; i < 100; i++ {
go tx.Where(...).First(&user) // `name = 'jinzhu'` will apply to the query
}