GORM 是一个功能强大的 Go 语言 ORM(对象关系映射)库,它提供了一种方便的方式来与 SQL 数据库进行交互,而不需要编写大量的 SQL 代码。
GORM的关键特性
-
全功能的ORM:支持几乎所有的ORM功能,包括模型定义、基本的CRUD操作、复杂查询、关联处理等。
-
关联支持:非常灵活的关联(has one, has many, belongs to, many to many, polymorphism, single-table inheritance)功能。
-
钩子(Hooks):支持在create/save/update/delete/find操作前后进行自定义处理。
-
预加载(Eager Loading):使用Preload, Joins等方式预加载关联数据。
-
事务处理:支持事务、嵌套事务、保存点以及回滚到保存点。
-
上下文支持: 支持上下文管理、准备语句模式、DryRun模式。
-
批量操作: 支持批量插入、分批次查询、通过Map进行查找/创建、使用SQL Expr和Context Valuer进行CRUD。
-
SQL构建器: 支持Upsert、锁定、优化器/索引/注释提示、命名参数以及子查询。
-
复合主键、索引、约束:对于复合主键、索引和约束也有很好的支持。
-
自动迁移(Auto Migrations): 支持自动数据库迁移。
-
日志: 支持日志记录功能。
-
插件API:提供可扩展、灵活的插件API, 如数据库解析器(支持多数据库、读写分离)/ Prometheus监控。
-
测试完备:每一个功能都伴随着对应的测试用例。
GORM的基本使用
安装
go get -u gorm.io/gorm
模型定义
在 GORM 中,模型通常由 Go 结构体表示,每一个模型对应数据库中的一个表。
type User struct {gorm.ModelName stringAge uintActive bool
}
在上面的代码中,gorm.Model
是一个包含了 ID, CreatedAt, UpdatedAt, DeletedAt 字段的基础模型。我们在此基础上添加了自定义字段(Name, Age, Active)。
数据库连接和配置
假设您已经有一个运行中的关系型数据库(比如 PostgreSQL、MySQL 等),您可以使用以下方式连接到数据库:
package mainimport ("gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm"
)type User struct {gorm.ModelName stringAge uintActive bool
}func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic("failed to connect database")}// 接下来可以使用 `db` 句柄进行数据库操作// 比如迁移模式db.AutoMigrate(&User{})
}
连接数据库是使用数据库之前必须要做的步骤,上述代码展示了如何使用GORM连接到MySQL数据库。
CRUD 操作
// 创建
user := User{Name: "Alice", Age: 20}
db.Create(&user)// 查询
var users []User
db.Find(&users)// 更新
db.Model(&user).Update("age", 21)// 删除
db.Delete(&user)
创建(C)
对数据库进行操作的第一步通常是向数据库中添加新记录。GORM使这个过程变得非常简单,示例如下:
上面的代码创建了一条新的用户记录。
package mainimport ("gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm"
)type User struct {gorm.ModelName stringAge uintActive bool
}func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic("failed to connect database")}// 接下来可以使用 `db` 句柄进行数据库操作// 比如迁移模式db.AutoMigrate(&User{})user := User{Name: "Jinzhu", Age: 18, Active: true}result := db.Create(&user) // 通过数据模型创建记录// 检查错误if result.Error != nil {panic(result.Error)}
}
查询(R)
读取或查询数据库中现有数据是 ORM 最常用的功能之一。GORM 提供了灵活的查询方法。以下是查询单个记录的示例:
上述代码从数据库中查询ID为1的用户记录,并打印出用户名。
package mainimport ("fmt""gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm"
)type User struct {gorm.ModelName stringAge uintActive bool
}func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic("failed to connect database")}// 接下来可以使用 `db` 句柄进行数据库操作// 比如迁移模式db.AutoMigrate(&User{})var user Userresult := db.First(&user, 1) // 查询ID为1的用户// 检查错误if result.Error != nil {panic(result.Error)}fmt.Println(user.Name)
}
更新(U)
更新现有记录是另一个常见的数据库操作。GORM 提供了多种更新方法,以下是如何更新一条记录的示例:
此代码将用户名更新为"Jin"
package mainimport ("gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm"
)type User struct {gorm.ModelName stringAge uintActive bool
}func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic("failed to connect database")}// 接下来可以使用 `db` 句柄进行数据库操作// 比如迁移模式db.AutoMigrate(&User{})var user Userdb.Model(&user).Update("Name", "Jin")
}
删除(D)
当需要从数据库中移除记录时,可以使用GORM的删除功能。在GORM中,删除可以是软删除(只更新DeletedAt字段,数据实际还在)或硬删除(实际从数据库中移除数据)。以下是删除一个用户的示例:
上面的代码将从数据库中删除ID为1的用户。
package mainimport ("gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm"
)type User struct {gorm.ModelName stringAge uintActive bool
}func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic("failed to connect database")}// 接下来可以使用 `db` 句柄进行数据库操作// 比如迁移模式db.AutoMigrate(&User{})var user Userdb.Delete(&user, 1) // 删除 ID 为1的用户
}
关联处理
GORM提供了强大的关联处理能力,它支持一对一、一对多、多对多等关系。下面是一个一对多关系的例子,其中User
有多个CreditCard
:
package mainimport ("gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm"
)type User struct {gorm.ModelCreditCards []CreditCard
}type CreditCard struct {gorm.ModelNumber stringUserID uint
}func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic("failed to connect database")}// 接下来可以使用 `db` 句柄进行数据库操作// 比如迁移模式db.AutoMigrate(&User{}, &CreditCard{})var user Userdb.Preload("CreditCards").Find(&user)
}
上述代码中,Preload 函数预加载了用户的所有信用卡记录。这就是使用GORM处理关联的一个例子。
type User struct {gorm.ModelName stringAge uintActive bool
}type Profile struct {gorm.ModelUser User
}// 一对多(Has Many)
type Post struct {gorm.ModelContent stringUser User
}// 设置和创建关联
user := User{Name: "Alice"}
db.Create(&user)
db.Model(&user).Association("Posts").Create(&Post{Content: "First post"})
以上代码创建User同时创建Post并关联User
GORM的高级使用
除了基本的CRUD操作,GORM还提供了高级功能,包括但不限于事务、Hooks、SQL构建器、日志记录等。这些功能可以帮助处理更复杂的场景,并使数据处理更加灵活和可控。
事务处理
tx := db.Begin()
defer func() {if r := recover(); r != nil {tx.Rollback()}
}()// 执行一些操作...
tx.Commit()
在复杂的操作中,您可能需要按照事务来执行一系列数据库操作,确保数据的一致性和完整性。GORM 使得处理事务变得简单。以下是一个使用事务的例子:
package mainimport ("gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm"
)type User struct {gorm.ModelCreditCards []CreditCard
}type CreditCard struct {gorm.ModelNumber stringUserID uint
}func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic("failed to connect database")}// 接下来可以使用 `db` 句柄进行数据库操作// 比如迁移模式db.AutoMigrate(&User{}, &CreditCard{})var user Uservar creditCard CreditCard// 开启一个事务tx := db.Begin()// 在事务中进行一系列操作tx.Create(&user)tx.Create(&creditCard)// 如果操作成功,则提交事务tx.Commit()// 如果中间产生了错误,您可以回滚这个事务tx.Rollback()
}
事务功能是确保数据安全性非常重要的一个功能。
钩子(Hooks)
GORM 允许您定义模型的钩子,例如在保存记录之前后自动执行特定功能。以下是定义BeforeSave
和AfterCreate
钩子的示例:
package mainimport ("fmt""gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm"
)type User struct {gorm.ModelName stringAge uintActive bool
}func (u *User) BeforeSave(tx *gorm.DB) (err error) {fmt.Println("每次Save操作之前,将打印相应的消息。")return
}func (u *User) AfterCreate(tx *gorm.DB) (err error) {fmt.Println("每次Create操作之后,将打印相应的消息。")return
}
func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {panic("failed to connect database")}// 接下来可以使用 `db` 句柄进行数据库操作// 比如迁移模式db.AutoMigrate(&User{})var user Userdb.Create(&user)db.Save(&user)
}
在以上代码中,每次Save
操作之前和Create
操作之后,将打印相应的消息。
type User struct {gorm.ModelName string
}// 钩子示例:在创建之前清空名字字段
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {u.Name = strings.ToUpper(u.Name)return
}// 使用
user := User{Name: "alice"}
db.Create(&user) // 用户名将被转换为 ALICE
SQL构建器和日志记录
GORM 的SQL构建器非常强大,提供了灵活的查询方式。同时,GORM 的日志记录功能使得调试和检查变得简单:
db.Where("name = ?", "jinzhu").First(&user)
上述代码展示了如何构造一个普通的查询,并且 GORM 会记录此次查询的日志输出。
调整日志级别
GORM 允许你设置不同的日志级别,以控制日志输出的详细程度。日志级别包括:
logger.Silent
:不输出任何日志信息。logger.Error
:只输出错误信息。logger.Warn
:输出错误和警告信息。logger.Info
:输出错误、警告和一般信息,包括慢查询。logger.Debug
:输出所有 SQL 语句和执行时间。
慢查询日志
SlowThreshold
配置项允许你设置慢查询的阈值。在这个阈值以上的查询将被视为慢查询,并在日志中特别标记出来。
彩色日志
Colorful
配置项允许你开启彩色日志输出,这可以使得日志输出更加易于阅读。
package mainimport ("fmt""gorm.io/driver/mysql" // 修改为相应的数据库驱动"gorm.io/gorm""gorm.io/gorm/logger""log""os""time"
)type User struct {gorm.ModelName stringAge uintActive bool
}func (u *User) BeforeSave(tx *gorm.DB) (err error) {fmt.Println("每次Save操作之前,将打印相应的消息。")return
}func (u *User) AfterCreate(tx *gorm.DB) (err error) {fmt.Println("每次Create操作之后,将打印相应的消息。")return
}
func main() {dsn := "username:password@protocol(address)/dbname?charset=utf8mb4&parseTime=True&loc=Local"_, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), // 日志输出logger.Config{SlowThreshold: 100 * time.Millisecond, // 慢查询阈值,任何执行时间超过 100 毫秒的查询都会被记录为慢查询LogLevel: logger.Info, // 日志级别Colorful: true, // 彩色日志},),})if err != nil {panic("failed to connect database")}
}
慢查询日志(Slow Query Log)是数据库管理系统中的一种日志记录功能,用于记录执行时间超过预设阈值的 SQL 查询。这个特性在 MySQL、PostgreSQL 等数据库中都有支持,并且 GORM 也提供了相应的配置来帮助开发者捕捉和记录这些慢查询。
理解慢查询日志的几个关键点:
-
阈值(Threshold):
- 阈值是指查询执行时间的上限,超过这个时间的查询将被认为是“慢查询”。
- 在 GORM 中,可以通过
logger.Config
的SlowThreshold
字段设置这个阈值。
-
日志记录:
- 慢查询日志记录了慢查询的详细信息,包括查询的 SQL 语句、执行时间、执行时的时间戳等。
- 这些信息对于分析性能瓶颈和优化数据库查询非常有用。
-
性能分析:
- 通过分析慢查询日志,开发者可以识别出影响数据库性能的查询语句。
- 可以进一步对这些查询进行优化,比如通过添加索引、改写查询逻辑或调整数据库结构。
-
实时监控:
- 在一些情况下,慢查询日志可以配置为实时输出,帮助开发者及时发现并处理性能问题。
-
资源消耗:
- 记录慢查询日志会消耗一定的系统资源,因为它需要额外的 I/O 操作来记录日志信息。
- 因此,通常在开发环境或性能测试时开启慢查询日志,而在生产环境中根据需要谨慎使用。
-
配置和使用:
- 在 GORM 中,慢查询日志的记录可以通过配置
logger.Config
来开启。 - 开启慢查询日志后,GORM 会在日志输出中标记出执行时间超过
SlowThreshold
的查询。
- 在 GORM 中,慢查询日志的记录可以通过配置
预加载
预加载可以减少数据库查询次数。
db.Preload("Profile").Find(&users)
var users []User
db.Preload("Profile").Find(&users, "age > ?", 18) // 预加载 Profile 关联并查询年龄大于 18 的用户
上下文支持
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()// 使用上下文执行查询
db.WithContext(ctx).Find(&users)
批量操作
users := []User{{Name: "Alice"}, {Name: "Bob"}}
db.CreateInBatches(users, 10) // 批量创建用户
SQL 构建器
db.Table("users").Select("name, age").Where("age >= ?", 20).Scan(&users)