方法链
GORM 的方法链特性支持流畅且自然的编码风格。以下是使用传统 API 和泛型 API 的示例:
传统 API
db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)泛型 API (>= v1.30.0)
ctx := context.Background()
user, err := gorm.G[User](db).Where("name = ?", "jinzhu").Where("age = ?", 18).First(ctx)两种 API 都支持方法链,但泛型 API 提供了增强的类型安全性,并且直接从操作方法中返回错误。
方法分类
GORM 将方法分为三个主要类别:链式方法、终结方法和新会话方法。这些分类同时适用于传统 API 和泛型 API。
链式方法
链式方法用于修改或向当前 Statement 追加 Clauses。常见的链式方法包括:
WhereSelectOmitJoinsScopesPreloadRaw(注意:Raw不能与其他可链式调用的方法组合使用来构建 SQL)
完整的方法列表请参阅 GORM Chainable API。此外,SQL Builder 文档提供了更多关于 Clauses 的详细信息。
终结方法
终结方法会立即执行注册的回调,生成并运行 SQL 命令。此类方法包括:
CreateFirstFindTakeSaveUpdateDeleteScanRowRows
完整的方法列表请参阅 GORM Finisher API。
新会话方法
GORM 将 Session、WithContext 和 Debug 等方法定义为新会话方法,它们对于创建可共享和可重用的 *gorm.DB 实例至关重要。更多详情请参阅 Session 文档。
可重用性与安全性
传统 API
GORM 传统 API 的一个重要方面是理解何时 *gorm.DB 实例可以安全重用。在执行链式方法或终结方法之后,GORM 会返回一个已初始化的 *gorm.DB 实例。该实例不安全重用,因为它可能携带之前操作的条件,从而导致 SQL 查询被污染。例如:
不安全重用的示例
queryDB := DB.Where("name = ?", "jinzhu")
queryDB.Where("age > ?", 10).First(&user)
queryDB.Where("age > ?", 20).First(&user2)
安全重用的示例
要安全地重用 *gorm.DB 实例,请使用新会话方法:
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})
queryDB.Where("age > ?", 10).First(&user)
queryDB.Where("age > ?", 20).First(&user2)
在此场景中,使用 Session(&gorm.Session{}) 确保每个查询都从全新的上下文开始,防止之前操作的条件污染 SQL 查询。这对于保持数据库交互的完整性和准确性至关重要。
泛型 API
GORM 泛型 API 的一个显著优势是它天然解决了 SQL 污染问题。使用泛型 API 时,你无需担心实例不安全重用,因为:
- 上下文直接传递给每个操作方法
- 错误直接从操作方法返回
- 泛型接口设计防止了条件污染
以下是一个泛型 API 安全处理方法链的示例:
ctx := context.Background()
genericDB := gorm.G[User](db).Where("name = ?", "jinzhu")
user1, err1 := genericDB.Where("age > ?", 10).First(ctx)
user2, err2 := genericDB.Where("age > ?", 20).First(ctx)
泛型 API 的设计显著降低了 SQL 污染的风险,使你的数据库交互更加可靠和可预测。
示例说明
让我们通过几个同时使用传统 API 和泛型 API 的示例来加以说明:
传统 API 示例
- 示例 1:安全的实例重用
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.Where("name = ?", "jinzhu").Where("age = ?", 18).Find(&users)
db.Where("name = ?", "jinzhu2").Where("age = ?", 20).Find(&users)
db.Find(&users)
在此示例中,每个方法调用链都是独立的,确保生成干净、未被污染的 SQL 查询。
- (反例)示例 2:不安全的实例重用
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
tx := db.Where("name = ?", "jinzhu")
tx.Where("age = ?", 18).Find(&users)
tx.Where("age = ?", 28).Find(&users)
在这个反例中,重用 tx 变量会导致条件叠加,这通常是不可取的。
- 示例 3:使用新会话方法进行安全重用
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
tx := db.Where("name = ?", "jinzhu").Session(&gorm.Session{})
tx := db.Where("name = ?", "jinzhu").WithContext(context.Background())
tx := db.Where("name = ?", "jinzhu").Debug()
tx.Where("age = ?", 18).Find(&users)
tx.Where("age = ?", 28).Find(&users)
在此示例中,使用新会话方法 Session、WithContext、Debug 为每个逻辑操作正确初始化了一个 *gorm.DB 实例,防止条件污染,并确保每个查询都是独立的,基于所提供的特定条件执行。
泛型 API 示例
- 示例 4:使用泛型 API 进行方法链调用
ctx := context.Background()
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
user, err := gorm.G[User](db).Where("name = ?", "jinzhu").Where("age = ?", 18).First(ctx)
genericDB := gorm.G[User](db).Where("name = ?", "jinzhu")
user1, err1 := genericDB.Where("age = ?", 18).First(ctx)
users, err2 := genericDB.Where("age > ?", 20).Find(ctx)
users, err3 := gorm.G[User](db).Raw("SELECT * FROM users WHERE name = ? AND age > ?", "jinzhu", 18).Find(ctx)在此示例中,泛型 API 在保持流畅方法链风格的同时提供了类型安全性。上下文直接传递给终结方法(First、Find),错误直接从这些方法返回,遵循 Go 的标准错误处理模式。
总的来说,这些示例说明了理解 GORM 在方法链和实例管理方面行为的重要性,以确保数据库查询的准确性和高效性。与传统 API 相比,泛型 API 提供了更类型安全、更不易出错的方法链方式。
评论
登录后参与评论
KnowForge