GORM事务关联
2025/12/23大约 8 分钟
GORM 提供了强大的事务和关联关系支持。本文通过完整可运行的示例,并结合 JPA 对比,帮助你掌握事务处理和关联关系操作。
GORM 事务与关联:事务处理、一对多、多对多
Java 对比:GORM 的事务类似
@Transactional,关联关系类似@OneToMany、@ManyToMany。
一、事务处理
1.1 基础事务
创建 transaction_basic.go:
nano transaction_basic.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出package main
import (
"fmt"
"errors"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Email string `gorm:"size:100;unique"`
}
type Account struct {
ID uint `gorm:"primaryKey"`
UserID uint
Balance int
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{}, &Account{})
// 1. 手动事务
tx := db.Begin()
// 创建用户
user := User{Name: "Alice", Email: "alice@example.com"}
if err := tx.Create(&user).Error; err != nil {
tx.Rollback()
panic(err)
}
// 创建账户
account := Account{UserID: user.ID, Balance: 1000}
if err := tx.Create(&account).Error; err != nil {
tx.Rollback()
panic(err)
}
// 提交事务
tx.Commit()
fmt.Println("Transaction committed successfully!")
// 2. 自动事务(推荐)
err := db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行操作
user2 := User{Name: "Bob", Email: "bob@example.com"}
if err := tx.Create(&user2).Error; err != nil {
return err // 自动回滚
}
account2 := Account{UserID: user2.ID, Balance: 2000}
if err := tx.Create(&account2).Error; err != nil {
return err // 自动回滚
}
// 返回 nil 自动提交
return nil
})
if err != nil {
fmt.Println("Transaction failed:", err)
} else {
fmt.Println("Auto transaction committed successfully!")
}
}运行 & 测试
go run transaction_basic.go
# Transaction committed successfully!
# Auto transaction committed successfully!Java 对比:
db.Transaction()≈@Transactional- 自动回滚 ≈ Spring 的
@Transactional(rollbackFor = Exception.class)
1.2 事务回滚示例
创建 transaction_rollback.go:
nano transaction_rollback.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出package main
import (
"fmt"
"errors"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Email string `gorm:"size:100;unique"`
}
type Account struct {
ID uint `gorm:"primaryKey"`
UserID uint
Balance int
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{}, &Account{})
// 故意触发错误,测试回滚
err := db.Transaction(func(tx *gorm.DB) error {
user := User{Name: "Charlie", Email: "charlie@example.com"}
if err := tx.Create(&user).Error; err != nil {
return err
}
fmt.Println("User created, ID:", user.ID)
// 故意返回错误,触发回滚
return errors.New("something went wrong")
})
if err != nil {
fmt.Println("Transaction rolled back:", err)
}
// 检查用户是否存在
var count int64
db.Model(&User{}).Where("name = ?", "Charlie").Count(&count)
fmt.Printf("User 'Charlie' count: %d (should be 0)\n", count)
}运行 & 测试
go run transaction_rollback.go
# User created, ID: 1
# Transaction rolled back: something went wrong
# User 'Charlie' count: 0 (should be 0)1.3 嵌套事务(SavePoint)
创建 transaction_savepoint.go:
nano transaction_savepoint.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Email string `gorm:"size:100;unique"`
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{})
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&User{Name: "Alice", Email: "alice@example.com"})
// 创建 SavePoint
tx.SavePoint("sp1")
tx.Create(&User{Name: "Bob", Email: "bob@example.com"})
// 回滚到 SavePoint
tx.RollbackTo("sp1")
return nil
})
// 检查结果
var count int64
db.Model(&User{}).Count(&count)
fmt.Printf("Total users: %d (should be 1, only Alice)\n", count)
}运行 & 测试
go run transaction_savepoint.go
# Total users: 1 (should be 1, only Alice)Java 对比:
- SavePoint ≈ JDBC 的
connection.setSavepoint()- 嵌套事务 ≈ Spring 的
PROPAGATION_NESTED
二、一对一关联
2.1 Has One(拥有一个)
创建 relation_has_one.go:
nano relation_has_one.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// 用户拥有一个账户
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Account Account // 一对一关联
}
type Account struct {
ID uint `gorm:"primaryKey"`
UserID uint // 外键
Balance int
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{}, &Account{})
// 1. 创建用户和账户
user := User{
Name: "Alice",
Account: Account{Balance: 1000},
}
db.Create(&user)
fmt.Println("User and account created")
// 2. 预加载查询
var result User
db.Preload("Account").First(&result, user.ID)
fmt.Printf("User: %s, Balance: %d\n", result.Name, result.Account.Balance)
// 3. 更新关联数据
db.Model(&result.Account).Update("balance", 2000)
fmt.Println("Account balance updated")
// 4. 删除关联(不会自动删除 Account)
db.Delete(&result)
var accountCount int64
db.Model(&Account{}).Count(&accountCount)
fmt.Printf("Account still exists: %d\n", accountCount)
}运行 & 测试
go run relation_has_one.go
# User and account created
# User: Alice, Balance: 1000
# Account balance updated
# Account still exists: 1Java 对比:
Has One≈@OneToOnePreload≈ JPA 的fetch = FetchType.EAGER
三、一对多关联
3.1 Has Many(拥有多个)
创建 relation_has_many.go:
nano relation_has_many.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// 用户拥有多个订单
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Orders []Order // 一对多关联
}
type Order struct {
ID uint `gorm:"primaryKey"`
UserID uint // 外键
Product string `gorm:"size:100"`
Amount int
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{}, &Order{})
// 1. 创建用户和订单
user := User{
Name: "Alice",
Orders: []Order{
{Product: "Laptop", Amount: 1},
{Product: "Mouse", Amount: 2},
{Product: "Keyboard", Amount: 1},
},
}
db.Create(&user)
fmt.Println("User and orders created")
// 2. 预加载查询
var result User
db.Preload("Orders").First(&result, user.ID)
fmt.Printf("User: %s, Orders: %d\n", result.Name, len(result.Orders))
for _, order := range result.Orders {
fmt.Printf(" - %s (x%d)\n", order.Product, order.Amount)
}
// 3. 添加新订单
newOrder := Order{UserID: user.ID, Product: "Monitor", Amount: 1}
db.Create(&newOrder)
fmt.Println("New order added")
// 4. 查询用户的所有订单
var orders []Order
db.Where("user_id = ?", user.ID).Find(&orders)
fmt.Printf("Total orders: %d\n", len(orders))
// 5. 删除某个订单
db.Delete(&Order{}, orders[0].ID)
fmt.Println("First order deleted")
}运行 & 测试
go run relation_has_many.go
# User and orders created
# User: Alice, Orders: 3
# - Laptop (x1)
# - Mouse (x2)
# - Keyboard (x1)
# New order added
# Total orders: 4
# First order deletedJava 对比:
Has Many≈@OneToManyOrders []Order≈List<Order> orders
四、多对多关联
4.1 Many To Many(多对多)
创建 relation_many_to_many.go:
nano relation_many_to_many.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// 学生和课程:多对多关系
type Student struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Courses []Course `gorm:"many2many:student_courses;"` // 多对多
}
type Course struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Students []Student `gorm:"many2many:student_courses;"` // 多对多
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&Student{}, &Course{})
// 1. 创建课程
math := Course{Name: "Math"}
physics := Course{Name: "Physics"}
chemistry := Course{Name: "Chemistry"}
db.Create(&math)
db.Create(&physics)
db.Create(&chemistry)
// 2. 创建学生并关联课程
alice := Student{
Name: "Alice",
Courses: []Course{math, physics},
}
bob := Student{
Name: "Bob",
Courses: []Course{physics, chemistry},
}
db.Create(&alice)
db.Create(&bob)
fmt.Println("Students and courses created")
// 3. 查询学生的课程
var student1 Student
db.Preload("Courses").First(&student1, alice.ID)
fmt.Printf("%s's courses:\n", student1.Name)
for _, course := range student1.Courses {
fmt.Printf(" - %s\n", course.Name)
}
// 4. 查询课程的学生
var course1 Course
db.Preload("Students").First(&course1, physics.ID)
fmt.Printf("%s course students:\n", course1.Name)
for _, student := range course1.Students {
fmt.Printf(" - %s\n", student.Name)
}
// 5. 添加关联
db.Model(&alice).Association("Courses").Append(&chemistry)
fmt.Println("Alice enrolled in Chemistry")
// 6. 删除关联
db.Model(&bob).Association("Courses").Delete(&chemistry)
fmt.Println("Bob dropped Chemistry")
// 7. 替换所有关联
db.Model(&alice).Association("Courses").Replace(&math)
fmt.Println("Alice now only takes Math")
// 8. 清空关联
db.Model(&bob).Association("Courses").Clear()
fmt.Println("Bob dropped all courses")
}运行 & 测试
go run relation_many_to_many.go
# Students and courses created
# Alice's courses:
# - Math
# - Physics
# Physics course students:
# - Alice
# - Bob
# Alice enrolled in Chemistry
# Bob dropped Chemistry
# Alice now only takes Math
# Bob dropped all coursesJava 对比:
many2many≈@ManyToManystudent_courses≈@JoinTable(name = "student_courses")
五、关联操作详解
5.1 Association 方法
创建 association_methods.go:
nano association_methods.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Orders []Order
}
type Order struct {
ID uint `gorm:"primaryKey"`
UserID uint
Product string `gorm:"size:100"`
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{}, &Order{})
user := User{Name: "Alice"}
db.Create(&user)
order1 := Order{Product: "Laptop"}
order2 := Order{Product: "Mouse"}
order3 := Order{Product: "Keyboard"}
// 1. Append - 添加关联
db.Model(&user).Association("Orders").Append(&order1, &order2)
fmt.Println("Orders appended")
// 2. Count - 统计关联数量
count := db.Model(&user).Association("Orders").Count()
fmt.Printf("Order count: %d\n", count)
// 3. Find - 查找关联
var orders []Order
db.Model(&user).Association("Orders").Find(&orders)
fmt.Println("Orders found:")
for _, o := range orders {
fmt.Printf(" - %s\n", o.Product)
}
// 4. Replace - 替换所有关联
db.Model(&user).Association("Orders").Replace(&order3)
fmt.Println("Orders replaced with Keyboard")
// 5. Delete - 删除关联(不删除记录本身)
db.Model(&user).Association("Orders").Delete(&order3)
fmt.Println("Association deleted")
// 6. Clear - 清空所有关联
db.Model(&user).Association("Orders").Append(&order1)
db.Model(&user).Association("Orders").Clear()
fmt.Println("All associations cleared")
}运行 & 测试
go run association_methods.go
# Orders appended
# Order count: 2
# Orders found:
# - Laptop
# - Mouse
# Orders replaced with Keyboard
# Association deleted
# All associations cleared六、预加载策略
6.1 不同的预加载方式
创建 preload_strategies.go:
nano preload_strategies.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Profile Profile
Orders []Order
}
type Profile struct {
ID uint `gorm:"primaryKey"`
UserID uint
Bio string `gorm:"size:200"`
}
type Order struct {
ID uint `gorm:"primaryKey"`
UserID uint
Product string `gorm:"size:100"`
}
func main() {
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.AutoMigrate(&User{}, &Profile{}, &Order{})
// 创建测试数据
user := User{
Name: "Alice",
Profile: Profile{Bio: "Software Engineer"},
Orders: []Order{
{Product: "Laptop"},
{Product: "Mouse"},
},
}
db.Create(&user)
// 1. 预加载单个关联
var user1 User
db.Preload("Profile").First(&user1, user.ID)
fmt.Printf("User: %s, Bio: %s\n", user1.Name, user1.Profile.Bio)
// 2. 预加载多个关联
var user2 User
db.Preload("Profile").Preload("Orders").First(&user2, user.ID)
fmt.Printf("User: %s, Orders: %d\n", user2.Name, len(user2.Orders))
// 3. 预加载所有关联
var user3 User
db.Preload("Profile").Preload("Orders").First(&user3, user.ID)
fmt.Printf("Fully loaded user: %s\n", user3.Name)
// 4. 条件预加载
var user4 User
db.Preload("Orders", "product = ?", "Laptop").First(&user4, user.ID)
fmt.Printf("Filtered orders: %d\n", len(user4.Orders))
}运行 & 测试
go run preload_strategies.go
# User: Alice, Bio: Software Engineer
# User: Alice, Orders: 2
# Fully loaded user: Alice
# Filtered orders: 1Java 对比:
Preload≈@EntityGraph或fetch = FetchType.EAGER- 条件预加载 ≈
@Where注解
七、对比总结
| 功能 | GORM | JPA/Hibernate | 说明 |
|---|---|---|---|
| 事务 | db.Transaction() | @Transactional | GORM 更灵活 |
| 一对一 | Has One | @OneToOne | 类似 |
| 一对多 | Has Many | @OneToMany | 类似 |
| 多对多 | many2many | @ManyToMany | GORM 自动建表 |
| 预加载 | Preload | @EntityGraph | GORM 更简洁 |
| 关联操作 | Association() | persist(cascade) | GORM 更灵活 |
八、常见问题
Q: 如何级联删除关联数据?
A: GORM 不自动级联删除,需要手动处理:
db.Select("Orders").Delete(&user) // 删除用户及其订单Q: 如何避免 N+1 查询问题?
A: 使用 Preload 预加载关联数据:
db.Preload("Orders").Find(&users)Q: 多对多关系如何自定义中间表?
A: 使用 joinTable 指定:
type Student struct {
Courses []Course `gorm:"many2many:enrollments;"`
}九、下一步
✅ 已掌握 GORM 事务和关联关系
→ 下一章:Viper 配置管理
→ 再下一章:Zap 日志系统
祝你编码愉快!🚀
