GORM allows method chaining, so you can write code like this:

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

There are three kinds of methods in GORM: Chain Method, Finisher Method, New Session Method.

After a Chain method, Finisher Method, GORM returns an initialized *gorm.DB instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example:

  1. queryDB := DB.Where("name = ?", "jinzhu")
  2. queryDB.Where("age > ?", 10).First(&user)
  3. // SELECT * FROM users WHERE name = "jinzhu" AND age > 10
  4. queryDB.Where("age > ?", 20).First(&user2)
  5. // SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20

In order to reuse a initialized *gorm.DB instance, you can use a New Session Method to create a shareable *gorm.DB, e.g:

  1. queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})
  2. queryDB.Where("age > ?", 10).First(&user)
  3. // SELECT * FROM users WHERE name = "jinzhu" AND age > 10
  4. queryDB.Where("age > ?", 20).First(&user2)
  5. // SELECT * FROM users WHERE name = "jinzhu" AND age > 20

Chain Method

Chain methods are methods to modify or add Clauses to current Statement, like:

Where, Select, Omit, Joins, Scopes, Preload, Raw (Raw can’t be used with other chainable methods to build SQL)…

Here is the full lists, also check out the SQL Builder for more details about Clauses.

Finisher Method

Finishers are immediate methods that execute registered callbacks, which will generate and execute SQL, like those methods:

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

Check out the full lists here.

New Session Method

GORM defined Session, WithContext, Debug methods as New Session Method, refer Session for more details.

After a Chain method, Finisher Method, GORM returns an initialized *gorm.DB instance, which is NOT safe to reuse anymore, you should use a New Session Method to mark the *gorm.DB as shareable.

Let’s explain it with examples:

Example 1:

  1. db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  2. // db is a new initialized `*gorm.DB`, which is safe to reuse
  3. db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
  4. // `Where("name = ?", "jinzhu")` is the first chain method call, it will create an initialized `*gorm.DB` instance, aka `*gorm.Statement`
  5. // `Where("age = ?", 18)` is the second chain method call, it reuses the above `*gorm.Statement`, adds new condition `age = 18` to it
  6. // `Find(&users)` is a finisher method, it executes registered Query Callbacks, which generates and runs the following SQL:
  7. // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18;
  8. db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users)
  9. // `Where("name = ?", "jinzhu2")` is also the first chain method call, it creates a new `*gorm.Statement`
  10. // `Where("age = ?", 20)` reuses the above `Statement`, and add conditions to it
  11. // `Find(&users)` is a finisher method, it executes registered Query Callbacks, generates and runs the following SQL:
  12. // SELECT * FROM users WHERE name = 'jinzhu2' AND age = 20;
  13. db.Find(&users)
  14. // `Find(&users)` is a finisher method call, it also creates a new `Statement` and executes registered Query Callbacks, generates and runs the following SQL:
  15. // SELECT * FROM users;

(Bad) Example 2:

  1. db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  2. // db is a new initialized *gorm.DB, which is safe to reuse
  3. tx := db.Where("name = ?", "jinzhu")
  4. // `Where("name = ?", "jinzhu")` returns an initialized `*gorm.Statement` instance after chain method `Where`, which is NOT safe to reuse
  5. // good case
  6. tx.Where("age = ?", 18).Find(&users)
  7. // `tx.Where("age = ?", 18)` use the above `*gorm.Statement`, adds new condition to it
  8. // `Find(&users)` is a finisher method call, it executes registered Query Callbacks, generates and runs the following SQL:
  9. // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18
  10. // bad case
  11. tx.Where("age = ?", 28).Find(&users)
  12. // `tx.Where("age = ?", 28)` also use the above `*gorm.Statement`, and keep adding conditions to it
  13. // So the following generated SQL is polluted by the previous conditions:
  14. // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18 AND age = 28;

Example 3:

  1. db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  2. // db is a new initialized *gorm.DB, which is safe to reuse
  3. tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{})
  4. tx := db.Where("name = ?", "jinzhu").WithContext(context.Background())
  5. tx := db.Where("name = ?", "jinzhu").Debug()
  6. // `Session`, `WithContext`, `Debug` returns `*gorm.DB` marked as safe to reuse, newly initialized `*gorm.Statement` based on it keeps current conditions
  7. // good case
  8. tx.Where("age = ?", 18).Find(&users)
  9. // SELECT * FROM users WHERE name = 'jinzhu' AND age = 18
  10. // good case
  11. tx.Where("age = ?", 28).Find(&users)
  12. // SELECT * FROM users WHERE name = 'jinzhu' AND age = 28;