GORM是Go语言中最流行的ORM库,提供了简洁的API和强大的功能。本文档详细介绍GORM框架的所有重要知识点,从基础使用到高级特性。
文档信息(用途、难度与维护记录)
| 属性 | 内容 |
|---|---|
| 模块 | 01-go-backend |
| 分类 | reference/framework-essentials |
| 难度 | ⭐⭐ |
| 标签 | #gorm #orm #数据库 #框架 |
| 更新日期 | 2026年9月 |
| 作者 | Dev Quest Team |
| 状态 | ✅ 已完成 |
GORM 将 Go 模型与关系数据库操作连接起来,能减少常见 CRUD 的重复代码,但生成 SQL、事务和索引仍需开发者理解。数据库驱动决定具体能力与差异;模型约定方便默认映射,已有表结构则可通过显式配置适配。先完成创建、查询与错误处理再展开关联。
在 Go module 中加入 GORM 和目标数据库驱动,记录并测试所选版本组合。导入 gorm.io/gorm 只提供 ORM API,具体连接还需 postgres 等驱动。不要把 go get -u 当每次安装必需,它可能扩大依赖更新范围;已有工程先审阅升级差异。
DSN 说明地址、数据库与身份等连接参数,不应把生产密码提交到文档或代码。通过 GORM 取得底层 sql.DB 管理池,启动时验证必要连接,退出时关闭。多数据库各有连接、事务和一致性边界,不因为持有多个句柄就自动拥有跨库原子性。
示例:
package main
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
"log"
"time"
)
func main() {
// 数据库连接
dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 获取底层sql.DB
sqlDB, err := db.DB()
if err != nil {
log.Fatal("Failed to get sql.DB:", err)
}
// 配置连接池
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
// 测试连接
if err := sqlDB.Ping(); err != nil {
log.Fatal("Database ping failed:", err)
}
log.Println("Database connected successfully")
}模型结构体描述字段,标签控制列名、主键或索引等映射,默认命名约定只是在没有显式设置时生效。先查看实际生成的表结构,确认 id、时间字段和可空性符合需求;HTTP 客户端能写哪些字段应另外用输入模型限制。
Go 值与数据库类型之间需明确缺失、零值和精度,例如 *string 或 nullable 类型可区分 NULL 与空串,金额不能随意转成 float。JSON、枚举与自定义类型依数据库和驱动支持,必要时实现 Scanner/Valuer 等契约。写入边界值再读回,检查没有精度或时区丢失。
primaryKey 标识主键,autoIncrement 请求相应自增行为,index/unique/not null 表达索引或约束意图,最终支持取决于数据库与迁移执行。唯一约束才防止并发下重复插入,“先查询不存在再创建”本身有竞态。验证重复值和 NULL 的实际失败结果。
示例:
package models
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Email string `gorm:"size:100;unique;not null"`
Age int `gorm:"default:18"`
Active bool `gorm:"default:true"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"`
// 自定义字段
Metadata JSON `gorm:"type:jsonb"`
// 关联关系
Profile *Profile `gorm:"foreignKey:UserID"`
Orders []Order `gorm:"foreignKey:UserID"`
}
type Profile struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"not null"`
Avatar string `gorm:"size:255"`
Bio string `gorm:"type:text"`
User User `gorm:"foreignKey:UserID"`
}
type Order struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"not null"`
Product string `gorm:"size:100;not null"`
Price float64 `gorm:"type:decimal(10,2)"`
Status string `gorm:"size:20;default:'pending'"`
CreatedAt time.Time `gorm:"autoCreateTime"`
User User `gorm:"foreignKey:UserID"`
}
// 自定义JSON类型
type JSON map[string]interface{}
func (j JSON) Value() (driver.Value, error) {
return json.Marshal(j)
}
func (j *JSON) Scan(value interface{}) error {
return json.Unmarshal(value.([]byte), &j)
}AutoMigrate 根据模型尝试补齐受支持的 schema 变化,返回错误必须处理;它不是带版本历史与自动回滚的完整迁移系统,也不会为保护数据随意删除旧列。生产变更需审阅锁表、数据回填与兼容影响,参见官方迁移说明。
Migrator 提供创建表、修改列和管理索引/约束的接口,但数据库对这些操作的能力与事务支持不同。删除列或表可能永久丢数据,迁移应包含前置检查和恢复设计。先对有代表性旧数据的测试库执行,并验证新旧应用过渡期间都能读写。
开发期可以快速重建合成数据,生产则需要保留已应用变更历史并评估兼容窗口。通常先添加兼容结构、回填数据、切换代码,再移除旧结构;迁移失败后的继续或恢复步骤必须明确。验收同时覆盖空库创建和旧库升级,不能只验证本机新数据库。
示例:
package main
import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// 自动迁移
func AutoMigrate(db *gorm.DB) error {
return db.AutoMigrate(
&User{},
&Profile{},
&Order{},
)
}
// 手动迁移
func ManualMigrate(db *gorm.DB) error {
// 创建表
if err := db.Migrator().CreateTable(&User{}); err != nil {
return err
}
// 添加字段
if err := db.Migrator().AddColumn(&User{}, "NewField"); err != nil {
return err
}
// 删除字段
if err := db.Migrator().DropColumn(&User{}, "OldField"); err != nil {
return err
}
// 创建索引
if err := db.Migrator().CreateIndex(&User{}, "Email"); err != nil {
return err
}
// 删除索引
if err := db.Migrator().DropIndex(&User{}, "EmailIndex"); err != nil {
return err
}
return nil
}
// 修改表结构
func ModifyTable(db *gorm.DB) error {
// 重命名表
if err := db.Migrator().RenameTable("users", "users_new"); err != nil {
return err
}
// 重命名列
if err := db.Migrator().RenameColumn(&User{}, "Name", "FullName"); err != nil {
return err
}
// 添加外键约束
if err := db.Migrator().CreateConstraint(&Profile{}, "User"); err != nil {
return err
}
return nil
}
// 检查表是否存在
func TableExists(db *gorm.DB, tableName string) (bool, error) {
return db.Migrator().HasTable(tableName)
}
// 检查列是否存在
func ColumnExists(db *gorm.DB, model interface{}, columnName string) (bool, error) {
return db.Migrator().HasColumn(model, columnName)
}Create 写入新记录,传入指针可取得回填的主键;传统链式 API 检查 Error,必要时检查 RowsAffected。批量创建减少往返但要控制批大小与失败语义。带默认值标签的字段要特别测试 0、false 等零值,确认数据库默认没有覆盖用户明确意图。
First 取得一条记录且可能返回 ErrRecordNotFound,Find 查询集合时零行通常通过空结果/影响行数表达,不能统一按同一种错误判断。Where 与排序决定取到什么,未指定稳定顺序的“第一条”不适合作业务时间顺序承诺。查零条、一条和多条验证契约。
Update 处理指定字段,Updates 可修改多个字段,目标是哪些行由 Model 与条件决定;“多字段”不等于必然“多行”。传统 API 的 struct Updates 默认忽略零值,map 或 Select 可显式写入 false/0。Save 的保存语义还可能涉及插入回退,避免在只允许更新的接口盲用,见官方更新说明。
Delete 需要明确目标条件,避免把空输入误解成全表删除。具有软删除字段的模型通常标记删除时间,普通查询过滤这些记录;Unscoped 等路径可能绕过保护或永久删除。软删除不等于隐私数据已擦除,恢复、保留期与清理要另设策略。
示例:
package services
import (
"gorm.io/gorm"
"errors"
)
type UserService struct {
db *gorm.DB
}
func NewUserService(db *gorm.DB) *UserService {
return &UserService{db: db}
}
// 创建用户
func (s *UserService) CreateUser(user *User) error {
if err := s.db.Create(user).Error; err != nil {
return err
}
return nil
}
// 批量创建用户
func (s *UserService) BatchCreateUsers(users []*User) error {
if err := s.db.CreateInBatches(users, 100).Error; err != nil {
return err
}
return nil
}
// 获取用户
func (s *UserService) GetUser(id uint) (*User, error) {
var user User
if err := s.db.First(&user, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("user not found")
}
return nil, err
}
return &user, nil
}
// 根据邮箱获取用户
func (s *UserService) GetUserByEmail(email string) (*User, error) {
var user User
if err := s.db.Where("email = ?", email).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("user not found")
}
return nil, err
}
return &user, nil
}
// 获取用户列表
func (s *UserService) GetUsers(page, limit int) ([]*User, int64, error) {
var users []*User
var total int64
// 计算总数
if err := s.db.Model(&User{}).Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页查询
offset := (page - 1) * limit
if err := s.db.Offset(offset).Limit(limit).Find(&users).Error; err != nil {
return nil, 0, err
}
return users, total, nil
}
// 更新用户
func (s *UserService) UpdateUser(user *User) error {
if err := s.db.Save(user).Error; err != nil {
return err
}
return nil
}
// 更新用户字段
func (s *UserService) UpdateUserFields(id uint, updates map[string]interface{}) error {
if err := s.db.Model(&User{}).Where("id = ?", id).Updates(updates).Error; err != nil {
return err
}
return nil
}
// 删除用户
func (s *UserService) DeleteUser(id uint) error {
if err := s.db.Delete(&User{}, id).Error; err != nil {
return err
}
return nil
}
// 软删除用户
func (s *UserService) SoftDeleteUser(id uint) error {
if err := s.db.Model(&User{}).Where("id = ?", id).Update("deleted_at", time.Now()).Error; err != nil {
return err
}
return nil
}
// 恢复软删除用户
func (s *UserService) RestoreUser(id uint) error {
if err := s.db.Model(&User{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
return err
}
return nil
}
// 永久删除用户
func (s *UserService) PermanentDeleteUser(id uint) error {
if err := s.db.Unscoped().Delete(&User{}, id).Error; err != nil {
return err
}
return nil
}Where 筛选、Order 排序、Limit/Offset 分页、Count 统计各承担不同责任。分页需要稳定排序并控制最大页大小,深偏移可能越来越贵。先用固定数据验证各页不重复遗漏,再用实际执行计划判断性能,不以链式调用短就认为 SQL 高效。
Join 组合表时可能把一条主记录展开成多行;Group 后 Having 过滤分组结果,而普通 Where 过滤参与分组的行。Distinct 的列集合决定什么算重复。先手工写出少量输入的预期行数,再检查生成 SQL 和 ORM 扫描结果,防止计数或分页被关联放大。
参数值通过占位符绑定,列名和排序方向等不能当普通值绑定的部分用允许列表选择。SQL 的 NULL 判断使用 IS NULL 等语义,不等同普通等号;AND/OR 加括号明确优先关系。测试空列表、NULL 和组合条件,避免意外扩大可访问记录范围。
链式方法累积查询条件,执行方法触发 SQL;复用已带条件的对象时要了解会话行为,防止条件污染下一次查询。查看 SQL 和绑定参数帮助理解真正执行了什么,日志须脱敏。查询结果缓存不是所有 GORM 查询默认提供的功能,需要单独说明实现和失效策略。
示例:
package repositories
import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type UserRepository struct {
db *gorm.DB
}
func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{db: db}
}
// 复杂查询示例
func (r *UserRepository) GetAdvancedQuery(params map[string]interface{}) ([]*User, error) {
query := r.db.Model(&User{})
// 动态条件
if name, ok := params["name"].(string); ok && name != "" {
query = query.Where("name LIKE ?", "%"+name+"%")
}
if email, ok := params["email"].(string); ok && email != "" {
query = query.Where("email LIKE ?", "%"+email+"%")
}
if ageMin, ok := params["age_min"].(int); ok {
query = query.Where("age >= ?", ageMin)
}
if ageMax, ok := params["age_max"].(int); ok {
query = query.Where("age <= ?", ageMax)
}
if active, ok := params["active"].(bool); ok {
query = query.Where("active = ?", active)
}
// 排序
if sortBy, ok := params["sort_by"].(string); ok {
order := sortBy
if sortOrder, ok := params["sort_order"].(string); ok {
order += " " + sortOrder
}
query = query.Order(order)
} else {
query = query.Order("created_at DESC")
}
// 分页
if page, ok := params["page"].(int); ok && page > 0 {
if limit, ok := params["limit"].(int); ok && limit > 0 {
offset := (page - 1) * limit
query = query.Offset(offset).Limit(limit)
}
}
var users []*User
if err := query.Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}
// Join查询示例
func (r *UserRepository) GetUsersWithProfiles() ([]*UserWithProfile, error) {
var results []*UserWithProfile
err := r.db.Model(&User{}).
Select("users.*, profiles.avatar, profiles.bio").
Joins("LEFT JOIN profiles ON profiles.user_id = users.id").
Where("users.active = ?", true).
Find(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
// 分组查询示例
func (r *UserRepository) GetUserCountByAge() ([]*AgeGroupCount, error) {
var results []*AgeGroupCount
err := r.db.Model(&User{}).
Select("age, COUNT(*) as count").
Group("age").
Order("age").
Find(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
// 子查询示例
func (r *UserRepository) GetUsersWithOrders() ([]*User, error) {
var users []*User
subQuery := r.db.Model(&Order{}).
Select("user_id").
Where("status = ?", "completed").
Group("user_id").
Having("COUNT(*) > ?", 5)
err := r.db.Where("id IN (?)", subQuery).Find(&users).Error
if err != nil {
return nil, err
}
return users, nil
}
// 原生SQL查询
func (r *UserRepository) GetUsersByRawSQL() ([]*User, error) {
var users []*User
sql := `
SELECT u.*
FROM users u
LEFT JOIN profiles p ON u.id = p.user_id
WHERE u.active = ? AND p.avatar IS NOT NULL
ORDER BY u.created_at DESC
`
err := r.db.Raw(sql, true).Scan(&users).Error
if err != nil {
return nil, err
}
return users, nil
}
type UserWithProfile struct {
User
Avatar string `json:"avatar"`
Bio string `json:"bio"`
}
type AgeGroupCount struct {
Age int `json:"age"`
Count int `json:"count"`
}Belongs To 与 Has One 的关键差别是外键放在哪一侧,例如订单持有 customer_id 表示属于客户,用户的 profile 可能由 profile.user_id 关联。Preload 显式加载关系,不应假定访问字段就自动发起懒查询。验证无关联记录时的结果形状。
Has Many 表示一条主记录关联多条子记录,如用户与订单,子表外键说明归属。加载用户列表后逐个查询订单可能产生 N+1,按需求批量预加载并控制数据量。修改关联和删除子记录是不同操作,执行后重新查询数据库确认效果。
多对多用中间表记录两端关系,例如学生与课程;若关系本身有成绩等属性,可显式建模中间表。唯一约束避免同一关系重复插入。解除关联通常只影响中间表,不等于删除课程或学生;分别测试添加、重复添加和解除关系。
外键和级联由数据库约束保护,ORM 的关联保存是应用侧操作,两者不能互相代替。选择删除行为前明确是阻止、级联还是置空,并测试真实数据库结果。预加载会增加数据与查询成本,按接口需要选择字段和关系,而非一次加载所有关联。
示例:
package models
import (
"gorm.io/gorm"
)
// 一对一关系
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Email string `gorm:"size:100;unique;not null"`
Profile *Profile `gorm:"foreignKey:UserID"`
}
type Profile struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"not null;unique"`
Avatar string `gorm:"size:255"`
Bio string `gorm:"type:text"`
User User `gorm:"foreignKey:UserID"`
}
// 一对多关系
type Post struct {
ID uint `gorm:"primaryKey"`
Title string `gorm:"size:200;not null"`
Content string `gorm:"type:text"`
UserID uint `gorm:"not null"`
User User `gorm:"foreignKey:UserID"`
Comments []Comment `gorm:"foreignKey:PostID"`
}
type Comment struct {
ID uint `gorm:"primaryKey"`
Content string `gorm:"type:text"`
PostID uint `gorm:"not null"`
Post Post `gorm:"foreignKey:PostID"`
}
// 多对多关系
type Student struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Courses []*Course `gorm:"many2many:student_courses;"`
}
type Course struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Description string `gorm:"type:text"`
Students []*Student `gorm:"many2many:student_courses;"`
}
type StudentCourse struct {
StudentID uint `gorm:"primaryKey"`
CourseID uint `gorm:"primaryKey"`
Grade int `gorm:"default:0"`
CreatedAt time.Time
}
// 关联操作示例
type UserService struct {
db *gorm.DB
}
func (s *UserService) GetUserWithProfile(id uint) (*User, error) {
var user User
// 预加载关联数据
if err := s.db.Preload("Profile").First(&user, id).Error; err != nil {
return nil, err
}
return &user, nil
}
func (s *UserService) GetUserWithPosts(id uint) (*User, error) {
var user User
// 预加载多个关联
if err := s.db.Preload("Profile").Preload("Posts").First(&user, id).Error; err != nil {
return nil, err
}
return &user, nil
}
func (s *UserService) GetUserWithNestedAssociations(id uint) (*User, error) {
var user User
// 嵌套预加载
if err := s.db.Preload("Profile").Preload("Posts.Comments").First(&user, id).Error; err != nil {
return nil, err
}
return &user, nil
}
func (s *UserService) CreateUserWithProfile(user *User, profile *Profile) error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 创建用户
if err := tx.Create(user).Error; err != nil {
return err
}
// 创建用户档案
profile.UserID = user.ID
if err := tx.Create(profile).Error; err != nil {
return err
}
return nil
})
}
func (s *UserService) UpdateUserProfile(userID uint, profile *Profile) error {
return s.db.Model(&User{}).Where("id = ?", userID).Updates(profile).Error
}
func (s *UserService) DeleteUserWithAssociations(id uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 删除用户档案
if err := tx.Where("user_id = ?", id).Delete(&Profile{}).Error; err != nil {
return err
}
// 删除用户
if err := tx.Delete(&User{}, id).Error; err != nil {
return err
}
return nil
})
}
// 多对多操作示例
type CourseService struct {
db *gorm.DB
}
func (s *CourseService) GetCourseWithStudents(courseID uint) (*Course, error) {
var course Course
if err := s.db.Preload("Students").First(&course, courseID).Error; err != nil {
return nil, err
}
return &course, nil
}
func (s *CourseService) EnrollStudent(courseID, studentID uint, grade int) error {
// 检查是否已存在关联
var count int64
if err := s.db.Model(&StudentCourse{}).
Where("student_id = ? AND course_id = ?", studentID, courseID).
Count(&count).Error; err != nil {
return err
}
if count > 0 {
// 更新成绩
return s.db.Model(&StudentCourse{}).
Where("student_id = ? AND course_id = ?", studentID, courseID).
Update("grade", grade).Error
}
// 创建新关联
return s.db.Create(&StudentCourse{
StudentID: studentID,
CourseID: courseID,
Grade: grade,
}).Error
}
func (s *CourseService) GetStudentCourses(studentID uint) ([]*Course, error) {
var student Student
if err := s.db.Model(&Student{}).
Where("id = ?", studentID).
Preload("Courses").
First(&student).Error; err != nil {
return nil, err
}
return student.Courses, nil
}一个事务让一组数据库操作一起提交或回滚。使用 Transaction 回调时,内部都用收到的 tx,返回错误触发回滚;手工 Begin/Commit/Rollback 则要检查每一步错误并覆盖所有退出路径。SavePoint/RollbackTo 可回退局部操作,但仍属于外层事务,见官方事务说明。
单次写入的默认事务不等于多次调用自动处在同一事务,需要显式包住完整业务步骤。嵌套事务通常通过保存点表达局部回退;跨数据库或消息系统的原子提交不由普通 GORM 事务自动提供。超时需通过 context/驱动与数据库配合,并确认失败后连接归还。
隔离级别约束并发事务可观察的变化,脏读、不可重复读与幻读分别关注未提交值、重复读值改变及结果集合变化,具体保证以数据库实现为准。更强隔离可能增加等待或重试;用两条并发事务实验验证余额/库存不变量,而不是仅写出级别名称。
事务边界覆盖必须一致的写入,例如创建订单与扣库存,不把慢外部请求混入持锁阶段。死锁或序列化冲突可按数据库错误分类考虑重试,但每次应重新执行完整事务并限制次数;重复外部副作用不能随意重放。记录事务耗时与失败原因,测试第二步失败时第一步也撤销。
示例:
package services
import (
"gorm.io/gorm"
"errors"
)
type OrderService struct {
db *gorm.DB
}
func NewOrderService(db *gorm.DB) *OrderService {
return &OrderService{db: db}
}
// 简单事务示例
func (s *OrderService) CreateOrder(order *Order, items []*OrderItem) error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 创建订单
if err := tx.Create(order).Error; err != nil {
return err
}
// 创建订单项
for _, item := range items {
item.OrderID = order.ID
if err := tx.Create(item).Error; err != nil {
return err
}
}
return nil
})
}
// 复杂事务示例
func (s *OrderService) ProcessOrder(orderID uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 获取订单
var order Order
if err := tx.First(&order, orderID).Error; err != nil {
return err
}
// 检查订单状态
if order.Status != "pending" {
return errors.New("order is not in pending status")
}
// 更新订单状态
if err := tx.Model(&order).Update("status", "processing").Error; err != nil {
return err
}
// 扣减库存
for _, item := range order.Items {
if err := s.updateInventory(tx, item.ProductID, -item.Quantity); err != nil {
return err
}
}
// 计算总价
totalAmount := s.calculateTotalAmount(order.Items)
// 创建支付记录
payment := &Payment{
OrderID: order.ID,
Amount: totalAmount,
Status: "pending",
}
if err := tx.Create(payment).Error; err != nil {
return err
}
// 更新订单状态为已完成
if err := tx.Model(&order).Update("status", "completed").Error; err != nil {
return err
}
return nil
})
}
// 嵌套事务示例
func (s *OrderService) BatchProcessOrders(orderIDs []uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
for _, orderID := range orderIDs {
if err := s.processSingleOrder(tx, orderID); err != nil {
return err
}
}
return nil
})
}
func (s *OrderService) processSingleOrder(tx *gorm.DB, orderID uint) error {
// 嵌套事务会自动回滚到保存点
return s.db.Transaction(func(nestedTx *gorm.DB) error {
var order Order
if err := nestedTx.First(&order, orderID).Error; err != nil {
return err
}
if order.Status != "pending" {
return errors.New("order is not pending")
}
return nestedTx.Model(&order).Update("status", "processing").Error
})
}
// 手动事务管理
func (s *OrderService) ManualTransaction() error {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
// 执行业务逻辑
if err := tx.Create(&Order{Status: "pending"}).Error; err != nil {
tx.Rollback()
return err
}
// 提交事务
return tx.Commit().Error
}
// 保存点示例
func (s *OrderService) SavepointExample() error {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
// 创建保存点
tx.SavePoint("sp1")
// 执行一些操作
if err := tx.Create(&Order{Status: "pending"}).Error; err != nil {
// 回滚到保存点
tx.RollbackTo("sp1")
return err
}
// 继续执行
if err := tx.Create(&Order{Status: "processing"}).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
// 更新库存(内部方法)
func (s *OrderService) updateInventory(tx *gorm.DB, productID uint, quantity int) error {
return tx.Model(&Product{}).
Where("id = ?", productID).
UpdateColumn("stock", gorm.Expr("stock + ?", quantity)).Error
}
// 计算总价(内部方法)
func (s *OrderService) calculateTotalAmount(items []*OrderItem) float64 {
var total float64
for _, item := range items {
total += item.Price * float64(item.Quantity)
}
return total
}模型写入钩子包括 BeforeSave/BeforeCreate、AfterCreate/AfterSave 以及更新、删除对应阶段;查询模型钩子是 AfterFind,BeforeFind 不是同样自动识别的模型钩子。写入后的 hook 可能仍在提交之前,不能据其名称就发送不可撤销邮件。具体时序见官方 hooks。
钩子采用框架要求的方法签名,返回错误能中止相应操作并影响当前事务。调用哪些钩子与使用的写入方法、批量形式和跳过配置有关,要测试实际调用路径;把函数命名得像 hook 不会自动让框架调用它。验证一次成功和一次 hook 拒绝时数据库状态。
钩子适合靠近模型的不变量或同事务审计写入,不适合隐藏复杂网络副作用。发送邮件或更新外部缓存若发生在数据库提交之前,后续回滚可能留下错误通知;可靠发送需要提交协调与重试设计。不要在 hook 中启动 goroutine 继续使用 tx,它可能已经结束。
示例:在同一事务中校验任务并记录审计行
下面是模型文件,不是独立的 main 程序。调用方先迁移 Task 与 TaskAudit,再通过 db.Create(&task) 创建任务。审计记录使用传入的 tx 同步写入;审计失败会让本次创建返回错误。在默认写入事务或调用方显式事务中,两次写入一起提交或回滚。若关闭默认事务且没有外层事务,就不再拥有这个原子性保证。
package models
import (
"errors"
"strings"
"unicode/utf8"
"gorm.io/gorm"
)
type Task struct {
ID uint `gorm:"primaryKey"`
Title string
TitleRunes int `gorm:"-"` // 读取后计算,不保存到数据库
}
type TaskAudit struct {
ID uint `gorm:"primaryKey"`
TaskID uint
Action string
}
func (t *Task) BeforeCreate(tx *gorm.DB) error {
t.Title = strings.TrimSpace(t.Title)
if t.Title == "" {
return errors.New("task title must not be empty")
}
return nil
}
func (t *Task) AfterCreate(tx *gorm.DB) error {
return tx.Create(&TaskAudit{TaskID: t.ID, Action: "created"}).Error
}
func (t *Task) AfterFind(tx *gorm.DB) error {
t.TitleRunes = utf8.RuneCountInString(t.Title)
return nil
}这里的字符数是 Unicode 码点数量,不保证等于用户看到的字形数量。AfterFind 只计算字段,不额外查询关联数据,避免查询一页任务时偷偷增加一串数据库请求。
验证时分别尝试:空白标题应返回错误且没有新任务;有效标题应同时产生任务和审计行;在外层 db.Transaction 中创建后主动返回错误,两张表都不应保留本次新增行。创建后钩子仍可能位于提交之前,因此发邮件等外部副作用不能照搬这个例子;可靠投递需要在事务里记录待发送事件,再由独立任务处理重试。
先查看慢查询的 SQL、参数规模与执行计划,判断索引是否服务筛选和排序。预加载解决部分 N+1,但加载过多关联也浪费资源;批量操作减少往返却可能扩大单次锁与内存。结果缓存需要另建失效契约,不能把“使用 GORM”当作自动拥有缓存。
池参数从底层 sql.DB 调整,观察等待、使用中和空闲连接,再结合数据库与副本数量设预算。事务未结束、Rows 未关闭都可能延长连接占用。模拟请求取消和查询失败后观察使用中连接回落,不把“连接池设置了数字”当作优化完成。
长事务会延长锁和连接持有,拆小前先确认业务是否允许分批成功。死锁可以通过统一锁顺序、减少范围等降低,重试只对已识别暂时性失败使用,并有上限与退避。比较同一负载下等待、失败与完成时间,而不单看每次事务的代码行数。
查询过多行和关联会同时增加数据库传输、扫描与 Go 堆分配。先限制结果范围或分批处理,记录峰值与持续负载后的内存,再判断是否存在长期持有。对象池有复用与清理成本,不能为所有模型默认添加;GC 参数也不能修复无界缓存。
示例:
package services
import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type OptimizedUserService struct {
db *gorm.DB
}
func NewOptimizedUserService(db *gorm.DB) *OptimizedUserService {
return &OptimizedUserService{db: db}
}
// 批量操作优化
func (s *OptimizedUserService) BatchCreateUsers(users []*User) error {
// 使用批量插入
return s.db.CreateInBatches(users, 100).Error
}
// 预加载优化
func (s *OptimizedUserService) GetUsersWithOptimizedPreload(page, limit int) ([]*User, int64, error) {
var users []*User
var total int64
// 只在需要时预加载
preload := func(db *gorm.DB) *gorm.DB {
return db.Preload("Profile", func(db *gorm.DB) *gorm.DB {
return db.Select("user_id", "avatar", "bio")
})
}
// 计算总数
if err := s.db.Model(&User{}).Count(&total).Error; err != nil {
return nil, 0, err
}
// 分页查询
offset := (page - 1) * limit
if err := s.db.Offset(offset).Limit(limit).Scopes(preload).Find(&users).Error; err != nil {
return nil, 0, err
}
return users, total, nil
}
// 查询优化 - 使用索引
func (s *OptimizedUserService) GetUsersByIndexSearch(name string, minAge, maxAge int) ([]*User, error) {
var users []*User
// 使用索引友好的查询
query := s.db.Model(&User{}).
Where("name LIKE ? AND age BETWEEN ? AND ?", name+"%", minAge, maxAge).
Order("age DESC, name ASC").
Limit(1000)
if err := query.Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}
// 原生SQL优化
func (s *OptimizedUserService) GetUsersByOptimizedRawSQL() ([]*User, error) {
var users []*User
// 使用优化的原生SQL
sql := `
SELECT u.id, u.name, u.email, u.age, u.created_at
FROM users u
WHERE u.active = true
AND u.created_at >= ?
ORDER BY u.created_at DESC
LIMIT ?
`
oneWeekAgo := time.Now().AddDate(0, 0, -7)
if err := s.db.Raw(sql, oneWeekAgo, 100).Scan(&users).Error; err != nil {
return nil, err
}
return users, nil
}
// 只选择需要的字段
func (s *OptimizedUserService) GetUserMinimalInfo(id uint) (*UserMinimalInfo, error) {
var info UserMinimalInfo
if err := s.db.Model(&User{}).
Select("id", "name", "email").
Where("id = ?", id).
First(&info).Error; err != nil {
return nil, err
}
return &info, nil
}
// 使用COUNT(1)优化
func (s *OptimizedUserService) GetUserCountByCondition(condition string) (int64, error) {
var count int64
if err := s.db.Model(&User{}).
Where(condition).
Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// 事务优化
func (s *OptimizedUserService) OptimizedTransaction() error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 批量操作
users := []*User{
{Name: "User1", Email: "user1@example.com"},
{Name: "User2", Email: "user2@example.com"},
}
if err := tx.CreateInBatches(users, len(users)).Error; err != nil {
return err
}
// 批量更新
if err := tx.Model(&User{}).
Where("name LIKE ?", "User%").
Update("active", true).Error; err != nil {
return err
}
return nil
})
}
// 连接池监控
func (s *OptimizedUserService) MonitorConnectionPool() map[string]interface{} {
sqlDB, err := s.db.DB()
if err != nil {
return nil
}
stats := sqlDB.Stats()
return map[string]interface{}{
"open_connections": stats.OpenConnections,
"in_use": stats.InUse,
"idle": stats.Idle,
"wait_count": stats.WaitCount,
"wait_duration": stats.WaitDuration,
"max_idle_closed": stats.MaxIdleClosed,
"max_idle_time_closed": stats.MaxIdleTimeClosed,
"max_lifetime_closed": stats.MaxLifetimeClosed,
}
}
// 查询性能监控
func (s *OptimizedUserService) ProfileQuery() ([]*User, error) {
var users []*User
// 启用查询日志
s.db.Logger = s.db.Logger.LogMode(4)
// 执行查询
err := s.db.Where("active = ?", true).Find(&users).Error
// 恢复日志级别
s.db.Logger = s.db.Logger.LogMode(1)
return users, err
}
// 使用事务重试机制
func (s *OptimizedUserService) RetryTransaction(maxRetries int, operation func(tx *gorm.DB) error) error {
var lastErr error
for i := 0; i < maxRetries; i++ {
err := s.db.Transaction(operation)
if err == nil {
return nil
}
lastErr = err
// 检查是否为可重试错误
if !isRetryableError(err) {
break
}
// 等待一段时间后重试
time.Sleep(time.Duration(i+1) * time.Second)
}
return lastErr
}
func isRetryableError(err error) bool {
// 判断错误是否为可重试错误
return strings.Contains(err.Error(), "deadlock") ||
strings.Contains(err.Error(), "connection reset") ||
strings.Contains(err.Error(), "timeout")
}
type UserMinimalInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}不依赖数据库的校验和业务计算可以直接测输入输出;模型标签、实际 hook 调用和关系保存则需要相应 ORM/数据库路径验证。用可控失败检查业务没有留下部分状态,避免 mock 掉整个被测行为后只验证调用脚本。
集成测试使用与目标数据库语义相符的环境,验证事务回滚、唯一约束、迁移和并发修改。SQLite 方便小实验但不能自动代表 PostgreSQL/MySQL 的隔离、类型和 SQL 方言。每条测试隔离数据,发布迁移还要测试旧结构升级而不仅新库创建。
Go testing 提供测试组织,断言库只改善表达,mock 用于明确的外部边界,容器数据库用于验证真实 SQL。夹具采用可识别合成数据,每例清理或重建。选择工具后先故意破坏一条约束,确认测试真的失败,而不是单纯检查命令退出成功。
示例:
package services_test
import (
"testing"
"github.com/stretchr/testify/assert"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"myapp/models"
"myapp/services"
)
// 设置测试数据库(testing.TB 同时兼容 *testing.T 与 *testing.B)
func setupTestDB(tb testing.TB) *gorm.DB {
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
tb.Fatal("Failed to connect to test database:", err)
}
// 自动迁移
err = db.AutoMigrate(&models.User{}, &models.Profile{}, &models.Order{})
if err != nil {
tb.Fatal("Failed to migrate test database:", err)
}
return db
}
// 用户服务测试
func TestUserService_CreateUser(t *testing.T) {
db := setupTestDB(t)
userService := services.NewUserService(db)
user := &models.User{
Name: "Test User",
Email: "test@example.com",
Age: 25,
}
err := userService.CreateUser(user)
assert.NoError(t, err)
assert.NotZero(t, user.ID)
// 验证用户是否创建成功
foundUser, err := userService.GetUser(user.ID)
assert.NoError(t, err)
assert.Equal(t, user.Name, foundUser.Name)
assert.Equal(t, user.Email, foundUser.Email)
}
func TestUserService_GetUserByEmail(t *testing.T) {
db := setupTestDB(t)
userService := services.NewUserService(db)
// 创建测试用户
user := &models.User{
Name: "Test User",
Email: "test@example.com",
Age: 25,
}
err := userService.CreateUser(user)
assert.NoError(t, err)
// 断言可按邮箱找回同一用户
foundUser, err := userService.GetUserByEmail("test@example.com")
assert.NoError(t, err)
assert.Equal(t, user.ID, foundUser.ID)
// 测试不存在的邮箱
_, err = userService.GetUserByEmail("nonexistent@example.com")
assert.Error(t, err)
assert.Contains(t, err.Error(), "user not found")
}
func TestUserService_UpdateUser(t *testing.T) {
db := setupTestDB(t)
userService := services.NewUserService(db)
// 创建测试用户
user := &models.User{
Name: "Test User",
Email: "test@example.com",
Age: 25,
}
err := userService.CreateUser(user)
assert.NoError(t, err)
// 更新用户
user.Name = "Updated User"
user.Age = 30
err = userService.UpdateUser(user)
assert.NoError(t, err)
// 验证更新
foundUser, err := userService.GetUser(user.ID)
assert.NoError(t, err)
assert.Equal(t, "Updated User", foundUser.Name)
assert.Equal(t, 30, foundUser.Age)
}
func TestUserService_DeleteUser(t *testing.T) {
db := setupTestDB(t)
userService := services.NewUserService(db)
// 创建测试用户
user := &models.User{
Name: "Test User",
Email: "test@example.com",
Age: 25,
}
err := userService.CreateUser(user)
assert.NoError(t, err)
// 删除用户
err = userService.DeleteUser(user.ID)
assert.NoError(t, err)
// 验证删除
_, err = userService.GetUser(user.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "user not found")
}
// 事务测试
func TestUserService_Transaction(t *testing.T) {
db := setupTestDB(t)
userService := services.NewUserService(db)
// 测试事务回滚
err := userService.db.Transaction(func(tx *gorm.DB) error {
user := &models.User{
Name: "Transaction User",
Email: "transaction@example.com",
Age: 25,
}
// 创建用户
if err := tx.Create(user).Error; err != nil {
return err
}
// 故意返回错误以触发回滚
return assert.AnError
})
assert.Error(t, err)
// 验证用户未创建
_, err = userService.GetUserByEmail("transaction@example.com")
assert.Error(t, err)
assert.Contains(t, err.Error(), "user not found")
}
// 批量操作测试
func TestUserService_BatchCreateUsers(t *testing.T) {
db := setupTestDB(t)
userService := services.NewUserService(db)
// 创建批量用户
users := []*models.User{
{Name: "User1", Email: "user1@example.com", Age: 25},
{Name: "User2", Email: "user2@example.com", Age: 30},
{Name: "User3", Email: "user3@example.com", Age: 35},
}
err := userService.BatchCreateUsers(users)
assert.NoError(t, err)
// 验证所有用户都创建成功
for _, user := range users {
foundUser, err := userService.GetUserByEmail(user.Email)
assert.NoError(t, err)
assert.Equal(t, user.Name, foundUser.Name)
}
}
// 钩子函数测试
func TestUser_Hooks(t *testing.T) {
db := setupTestDB(t)
userService := services.NewUserService(db)
// 测试创建钩子
user := &models.User{
Name: "Hook User",
Email: "hook@example.com",
Password: "password123", // 应该被加密
Age: 25,
}
err := userService.CreateUser(user)
assert.NoError(t, err)
// 验证密码被加密
assert.NotEqual(t, "password123", user.Password)
assert.Contains(t, user.Password, "hashed_")
// 测试更新钩子
user.Password = "newpassword123"
err = userService.UpdateUser(user)
assert.NoError(t, err)
// 验证新密码被加密
assert.NotEqual(t, "newpassword123", user.Password)
assert.Contains(t, user.Password, "hashed_")
}
// 关联关系测试
func TestUser_Associations(t *testing.T) {
db := setupTestDB(t)
userService := services.NewUserService(db)
// 创建用户
user := &models.User{
Name: "Association User",
Email: "association@example.com",
Age: 25,
}
err := userService.CreateUser(user)
assert.NoError(t, err)
// 创建用户档案
profile := &models.Profile{
UserID: user.ID,
Avatar: "avatar.jpg",
Bio: "Test bio",
}
err = userService.CreateUserProfile(profile)
assert.NoError(t, err)
// 测试预加载
userWithProfile, err := userService.GetUserWithProfile(user.ID)
assert.NoError(t, err)
assert.NotNil(t, userWithProfile.Profile)
assert.Equal(t, profile.Avatar, userWithProfile.Profile.Avatar)
}
// 性能基准测试
func BenchmarkUserService_CreateUser(b *testing.B) {
db := setupTestDB(b)
userService := services.NewUserService(db)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
user := &models.User{
Name: "Benchmark User",
Email: "benchmark@example.com",
Age: 25,
}
err := userService.CreateUser(user)
if err != nil {
b.Error(err)
}
}
})
}
func BenchmarkUserService_GetUser(b *testing.B) {
db := setupTestDB(b)
userService := services.NewUserService(db)
// 创建测试用户
user := &models.User{
Name: "Benchmark User",
Email: "benchmark@example.com",
Age: 25,
}
err := userService.CreateUser(user)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := userService.GetUser(user.ID)
if err != nil {
b.Error(err)
}
}
}区分未找到、唯一约束冲突、连接失败与业务验证错误,因为调用者的恢复动作不同。传统链式 API 通过 Error 取结果,支持的错误翻译还需相应配置;底层数据库错误仍按驱动契约识别。不要靠任意字符串包含关系决定是否重试。
发生错误后先判断能否恢复,再补充操作上下文并传播;事务中的失败不能被吞掉后返回 nil。HTTP 边界把内部错误翻译为稳定响应,日志记录必要原因并脱敏。对暂时故障重试前确认幂等和总时限,永久格式错误应及时返回给调用者。
自定义错误保留稳定类别与必要上下文,包装时使用可追溯原因的方式,使 errors.Is/As 仍能判断底层问题。错误码用于客户端决策,文案用于解释,日志用于内部排查,三者不用共享同一条含 SQL 的字符串。为每个公开错误写一个可触发的请求例子。
示例:
package errors
import (
"errors"
"fmt"
"gorm.io/gorm"
)
// 自定义错误类型
type GormError struct {
Code string
Message string
Details interface{}
Err error
}
func (e *GormError) Error() string {
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
func (e *GormError) Unwrap() error {
return e.Err
}
// 错误码定义
const (
ErrCodeNotFound = "NOT_FOUND"
ErrCodeDuplicate = "DUPLICATE"
ErrCodeInvalidInput = "INVALID_INPUT"
ErrCodeDatabase = "DATABASE_ERROR"
ErrCodeTransaction = "TRANSACTION_ERROR"
ErrCodeValidation = "VALIDATION_ERROR"
ErrCodeConnection = "CONNECTION_ERROR"
)
// 错误处理函数
func HandleGormError(err error) *GormError {
if err == nil {
return nil
}
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
return &GormError{
Code: ErrCodeNotFound,
Message: "Record not found",
Err: err,
}
case errors.Is(err, gorm.ErrInvalidTransaction):
return &GormError{
Code: ErrCodeTransaction,
Message: "Invalid transaction",
Err: err,
}
case errors.Is(err, gorm.ErrNotImplemented):
return &GormError{
Code: ErrCodeDatabase,
Message: "Not implemented",
Err: err,
}
case errors.Is(err, gorm.ErrMissingWhereClause):
return &GormError{
Code: ErrCodeInvalidInput,
Message: "Missing where clause",
Err: err,
}
case errors.Is(err, gorm.ErrUnsupportedRelation):
return &GormError{
Code: ErrCodeInvalidInput,
Message: "Unsupported relation",
Err: err,
}
case errors.Is(err, gorm.ErrPrimaryKeyRequired):
return &GormError{
Code: ErrCodeValidation,
Message: "Primary key required",
Err: err,
}
case errors.Is(err, gorm.ErrModelValueRequired):
return &GormError{
Code: ErrCodeValidation,
Message: "Model value required",
Err: err,
}
case errors.Is(err, gorm.ErrInvalidData):
return &GormError{
Code: ErrCodeInvalidInput,
Message: "Invalid data",
Err: err,
}
}
// 处理数据库特定错误
if isDuplicateKeyError(err) {
return &GormError{
Code: ErrCodeDuplicate,
Message: "Duplicate key error",
Err: err,
}
}
if isConnectionError(err) {
return &GormError{
Code: ErrCodeConnection,
Message: "Database connection error",
Err: err,
}
}
// 默认错误
return &GormError{
Code: ErrCodeDatabase,
Message: "Database error",
Details: err.Error(),
Err: err,
}
}
// 检查是否为重复键错误
func isDuplicateKeyError(err error) bool {
return strings.Contains(err.Error(), "duplicate key") ||
strings.Contains(err.Error(), "Duplicate entry") ||
strings.Contains(err.Error(), "unique constraint")
}
// 检查是否为连接错误
func isConnectionError(err error) bool {
return strings.Contains(err.Error(), "connection refused") ||
strings.Contains(err.Error(), "no connection") ||
strings.Contains(err.Error(), "connection reset") ||
strings.Contains(err.Error(), "timeout")
}
// 包装错误
func WrapGormError(err error, message string) *GormError {
if err == nil {
return nil
}
gormErr := HandleGormError(err)
gormErr.Message = message + ": " + gormErr.Message
return gormErr
}
// 创建错误
func NewGormError(code, message string, details interface{}) *GormError {
return &GormError{
Code: code,
Message: message,
Details: details,
}
}
// 使用示例
package services
import (
"myapp/errors"
"gorm.io/gorm"
)
type UserService struct {
db *gorm.DB
}
func (s *UserService) GetUser(id uint) (*User, error) {
var user User
err := s.db.First(&user, id).Error
if err != nil {
gormErr := errors.HandleGormError(err)
return nil, gormErr
}
return &user, nil
}
func (s *UserService) CreateUser(user *User) error {
err := s.db.Create(user).Error
if err != nil {
gormErr := errors.WrapGormError(err, "Failed to create user")
gormErr.Details = map[string]interface{}{
"name": user.Name,
"email": user.Email,
}
return gormErr
}
return nil
}
func (s *UserService) UpdateUser(user *User) error {
err := s.db.Save(user).Error
if err != nil {
return errors.WrapGormError(err, "Failed to update user")
}
return nil
}
func (s *UserService) DeleteUser(id uint) error {
err := s.db.Delete(&User{}, id).Error
if err != nil {
gormErr := errors.HandleGormError(err)
if gormErr.Code == errors.ErrCodeNotFound {
// 找不到记录不算错误
return nil
}
return gormErr
}
return nil
}
// 事务错误处理
func (s *UserService) ProcessOrder(orderID uint) error {
err := s.db.Transaction(func(tx *gorm.DB) error {
var order Order
if err := tx.First(&order, orderID).Error; err != nil {
return errors.WrapGormError(err, "Failed to get order")
}
// 处理订单逻辑
if err := s.processOrderInternal(tx, &order); err != nil {
return err
}
return nil
})
if err != nil {
return errors.WrapGormError(err, "Failed to process order")
}
return nil
}
// 错误中间件
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
// 检查是否有错误
if len(c.Errors) > 0 {
err := c.Errors[0].Err
// 转换为GORM错误
gormErr := errors.HandleGormError(err)
// 根据错误码返回不同的HTTP状态码
var statusCode int
switch gormErr.Code {
case errors.ErrCodeNotFound:
statusCode = 404
case errors.ErrCodeInvalidInput, errors.ErrCodeValidation:
statusCode = 400
case errors.ErrCodeDuplicate:
statusCode = 409
case errors.ErrCodeConnection:
statusCode = 503
default:
statusCode = 500
}
c.JSON(statusCode, gin.H{
"code": gormErr.Code,
"message": gormErr.Message,
"details": gormErr.Details,
})
}
}
}myapp/
├── cmd/
│ └── main.go
├── internal/
│ ├── models/
│ │ ├── user.go
│ │ ├── profile.go
│ │ └── migrations.go
│ ├── repositories/
│ │ ├── user_repository.go
│ │ └── order_repository.go
│ ├── services/
│ │ ├── user_service.go
│ │ └── order_service.go
│ ├── errors/
│ │ └── gorm_errors.go
│ └── config/
│ └── database.go
├── pkg/
│ ├── database/
│ │ └── gorm.go
│ └── logger/
│ └── logger.go
├── migrations/
│ ├── 001_create_users_table.up.sql
│ └── 001_create_users_table.down.sql
├── tests/
│ ├── services/
│ │ └── user_service_test.go
│ └── repositories/
│ └── user_repository_test.go
└── docs/
└── gorm_guide.md
从查询与业务不变量反推模型:哪些字段可空、何者唯一、关联由谁拥有、删除后发生什么。索引根据实际筛选/排序建立,写入成本与存储成本也需考虑。应用校验改善反馈,数据库约束抵御并发竞态,两者共同验证重复值和越界状态。
选一个具体慢接口,保存其 SQL、执行计划、数据量与延迟基线;改变索引、投影字段或加载方式后在同等负载复测。不要把批量、预加载和缓存一次全部加入,否则难以知道哪个改动有效,也容易引入新的一致性问题。
用一条失败实验定义事务承诺:扣库存成功后创建订单失败,最终库存应保持原值。确保两步使用同一个 tx,提交结果被检查。只有明确可重试的数据库冲突才重新执行整个业务单元,日志记录次数与最终结果而非秘密参数。
错误处理的验收是调用方知道能否修改输入、重试或联系支持,运维能够查到真实原因。对未找到、冲突和连接失败分别编写用例,确认状态不同且未泄漏内部信息;恢复动作失败时继续传播,不用返回空列表掩盖数据库不可用。
这个GORM ORM知识点文档涵盖了GORM框架的所有重要方面,从基础使用到高级特性,从开发实践到部署运维。掌握这些知识点将帮助你成为一名熟练的GORM框架开发者。