准备工作
- 配置好MySQL 数据库
- 导入GORM 库 (go get -u gorm.io/gorm)与 MySQL 驱动 (go get -u gorm.io/driver/mysql)
设置数据库连接
假设本地有一个名为 testdb的数据库,用户名和密码分别为 root 和 password
package main
import (
"database/sql"
"fmt"
"log"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
panic("failed to connect database")
}
}
定义模型
type User struct {
ID uint
Name string
// 假设 Profile 是通过 ProfileID 外键关联的
ProfileID uint
Profile Profile `gorm:"foreignKey:ProfileID"`
}
type Profile struct {
ID uint
UserID uint
Bio string
}
实现CRUD操作
// 自动迁移模式
db.AutoMigrate(&User{}, &Profile{})
// 创建一些示例数据
profile := Profile{Bio: "I love coding!"}
db.Create(&profile)
user := User{Name: "John Doe", ProfileID: profile.ID}
db.Create(&user)
// 使用 Preload 方法预加载关联数据
// 当你查询`User`记录时,GORM会自动加载与每个`User`相关联的`Profile`记录,并将它们作为嵌套结构填充到`User`实例中。
var usersWithProfiles []User
db.Preload("Profile").Find(&usersWithProfiles)
for _, user := range usersWithProfiles {
fmt.Printf("User: %s, Bio: %s\n", user.Name, user.Profile.Bio)
}
// 使用原生 SQL 查询
rows, err := db.Raw("SELECT users.*, profiles.bio FROM users LEFT JOIN profiles ON users.profile_id = profiles.id WHERE users.name = ?", "John").Rows()
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// 读取并打印原生SQL查询结果
var userName, bio string
for rows.Next() {
if err := rows.Scan(&userName, &bio); err != nil {
log.Fatal(err)
}
fmt.Printf("User: %s, Bio: %s\n", userName, bio)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
// 使用 Joins 方法执行自定义联接查询
var userProfiles []struct { Name string `gorm:"column:users.name"` Bio string `gorm:"column:profiles.bio"` }
db.Table("users").
Select("users.name, profiles.bio").
Joins("LEFT JOIN profiles ON users.profile_id = profiles.id").
Scan(&userProfiles)
for _, profile := range userProfiles {
fmt.Printf("Name: %s, Bio: %s\n", profile.User.Name, profile.Bio)
}
在 GORM 中,db.AutoMigrate() 用于自动迁移模型(在这个例子中是 User 和Profile模型)到数据库中。GORM 会检查数据库中是否存在与模型相对应的表,如果不存在,GORM 会创建这个表,并根据模型的字段定义来设置表的列。如果表已经存在,GORM 会尝试更新表结构以匹配模型定义,例如添加缺失的列或修改列的数据类型(GORM 不会删除任何列或更改现有数据的类型,除非已在模型中明确指定了GORM 能够安全处理的更改)。
事务处理
// 开始事务
tx := db.Begin()
// 在事务中执行数据库操作
user1 := User{Name: "John Doe"}
user2 := User{Name: "Jane Doe"}
if err := tx.Create(&user1).Error; err != nil {
tx.Rollback() // 如果发生错误,回滚事务
fmt.Println("Error creating user1:", err)
return
}
if err := tx.Create(&user2).Error; err != nil {
tx.Rollback() // 如果发生错误,回滚事务
fmt.Println("Error creating user2:", err)
return
}
// 提交事务
if err := tx.Commit().Error; err != nil {
fmt.Println("Error committing transaction:", err)
return
}
fmt.Println("Transaction committed successfully")
HOOK
在GORM中,Hook(钩子)是一种在数据库操作之前或之后自动调用的函数。这些钩子函数允许开发者在数据库操作的特定阶段执行自定义逻辑,例如验证数据、设置时间戳、更新缓存、发送通知等。通过使用钩子,开发者可以很方便地在数据库操作的各个生命周期阶段执行自定义逻辑,从而大幅增强 GORM 的灵活性和实用性。
GORM 的钩子机制主要有以下几个:
- BeforeCreate:在创建记录之前调用。
- AfterCreate:在创建记录之后调用。
- BeforeUpdate:在更新记录之前调用。
- AfterUpdate:在更新记录之后调用。
- BeforeDelete:在删除记录之前调用。
- AfterDelete:在删除记录之后调用。
- BeforeFind:在查询记录之前调用(GORM v2 中已移除)。
- AfterFind:在查询记录之后调用(GORM v2 中已移除,可以通过
Find方法的回调实现类似功能)。 - BeforeSave:在创建或更新记录之前调用(GORM v2 引入)。
- AfterSave:在创建或更新记录之后调用(GORM v2 引入)。
- RowQuery:在执行 SQL 查询之前调用,用于修改查询条件。
- Row:在查询单条记录后调用,可以修改查询结果。
以BeforeCreate和AfterCreat为例:
type User struct {
gorm.Model // 包含 `ID`,`CreatedAt` 和 `UpdatedAt`(记录创建和更新的时间戳),以及 `DeletedAt`(用于软删除的时间戳)
Name string
Email string
}
// BeforeCreate 钩子:在创建记录之前调用
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
// 自定义逻辑:设置默认的 Email 域名
u.Email = u.Email + "@outloook.com"
return nil
}
// AfterCreate 钩子:在创建记录之后调用
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
// 自定义逻辑:打印创建后的用户信息
fmt.Println("User created:", u.Name, u.Email)
return nil
}
func main() {
// 数据库连接代码省略
// 创建用户
user := User{Name: "John", Email: "john"}
db.Create(&user)
}