Go Web 编程快速入门 20 - 附录D:ORM 简介(可选,GORM)
1 ORM 概述
1.1 什么是 ORM
写过数据库代码的人都知道,每次操作数据库都要写一堆SQL语句,还要处理结果集映射,代码重复又容易出错。ORM(Object-Relational Mapping,对象关系映射)就是来解决这个问题的。
简单说,ORM让你可以用操作对象的方式来操作数据库。不用写SQL,直接调用方法就行。比如要查询用户,原来要写SELECT * FROM users WHERE id = ?,现在直接user.FindByID(1)就搞定了。
1.2 ORM 的优缺点
用ORM确实爽,但也不是万能的。
好处很明显:
- 写代码快多了,不用反复写SQL
- 编译时就能发现错误,不用等到运行时才崩溃
- 换数据库方便,MySQL换PostgreSQL基本不用改代码
- 连接池、事务这些都帮你处理好了
- 代码看起来更清晰,一眼就知道在干什么
但也有坑:
- 多了一层抽象,性能肯定有损耗
- 学习成本不低,每个ORM都有自己的语法
- 复杂查询写起来别扭,有时候还不如直接写SQL
- 出问题时不好调试,生成的SQL可能和你想的不一样
1.3 Go 语言中的 ORM 选择
Go生态里的ORM框架不少:
- GORM:功能最全,用的人最多
- Ent:Facebook出品,代码生成式
- SQLBoiler:也是代码生成式,性能不错
- Beego ORM:Beego框架自带的
- XORM:比较轻量,上手简单
这里主要讲GORM,毕竟是Go语言里最火的ORM框架。
2 GORM 入门
2.1 安装和配置
先把GORM装上,顺便把数据库驱动也装了:
# 初始化项目
go mod init gorm-example# 安装GORM核心包
go get -u gorm.io/gorm# 根据你用的数据库选择对应驱动
go get -u gorm.io/driver/postgres # PostgreSQL
go get -u gorm.io/driver/mysql # MySQL
go get -u gorm.io/driver/sqlite # SQLite(开发测试用)
go get -u gorm.io/driver/sqlserver # SQL Server
2.2 基本连接
连接数据库是第一步,GORM支持主流的几种数据库:
package mainimport ("fmt""log""time""gorm.io/driver/postgres""gorm.io/driver/mysql""gorm.io/driver/sqlite""gorm.io/gorm""gorm.io/gorm/logger"
)// DatabaseConfig 数据库配置结构体
type DatabaseConfig struct {Host stringPort intUser stringPassword stringDBName stringSSLMode string
}// ConnectPostgreSQL 连接PostgreSQL数据库
func ConnectPostgreSQL(config DatabaseConfig) (*gorm.DB, error) {dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s",config.Host, config.User, config.Password, config.DBName, config.Port, config.SSLMode)db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Info), // 开启SQL日志,方便调试})if err != nil {return nil, fmt.Errorf("连接PostgreSQL失败: %w", err)}return db, nil
}// ConnectMySQL 连接MySQL数据库
func ConnectMySQL(config DatabaseConfig) (*gorm.DB, error) {dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",config.User, config.Password, config.Host, config.Port, config.DBName)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Info),})if err != nil {return nil, fmt.Errorf("连接MySQL失败: %w", err)}return db, nil
}// ConnectSQLite 连接SQLite数据库(开发测试用)
func ConnectSQLite(dbPath string) (*gorm.DB, error) {db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{Logger: logger.Default.LogMode(logger.Info),})if err != nil {return nil, fmt.Errorf("连接SQLite失败: %w", err)}return db, nil
}// ConfigureConnectionPool 配置数据库连接池
func ConfigureConnectionPool(db *gorm.DB) error {sqlDB, err := db.DB()if err != nil {return fmt.Errorf("获取底层数据库连接失败: %w", err)}// 连接池配置,根据实际情况调整sqlDB.SetMaxIdleConns(10) // 空闲连接数sqlDB.SetMaxOpenConns(100) // 最大连接数sqlDB.SetConnMaxLifetime(time.Hour) // 连接最长存活时间sqlDB.SetConnMaxIdleTime(10 * time.Minute) // 连接最长空闲时间return nil
}func main() {// 开发阶段用SQLite比较方便db, err := ConnectSQLite("test.db")if err != nil {log.Fatal("数据库连接失败:", err)}// 配置连接池err = ConfigureConnectionPool(db)if err != nil {log.Fatal("配置连接池失败:", err)}fmt.Println("数据库连接成功!")
}
2.3 模型定义
GORM的核心就是模型定义,把数据库表映射成Go结构体。先来看看基础的模型结构:
package modelsimport ("time""gorm.io/gorm"
)// BaseModel 基础模型,包含常用的几个字段
type BaseModel struct {ID uint `gorm:"primarykey" json:"id"`CreatedAt time.Time `json:"created_at"`UpdatedAt time.Time `json:"updated_at"`DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"` // 软删除
}// User 用户模型
type User struct {BaseModelUsername string `gorm:"uniqueIndex;size:50;not null" json:"username"`Email string `gorm:"uniqueIndex;size:100;not null" json:"email"`Password string `gorm:"size:255;not null" json:"-"` // 密码不返回给前端Age int `gorm:"check:age >= 0" json:"age"`IsActive bool `gorm:"default:true" json:"is_active"`Profile Profile `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"profile,omitempty"`Posts []Post `gorm:"foreignKey:UserID" json:"posts,omitempty"`Orders []Order `gorm:"foreignKey:UserID" json:"orders,omitempty"`
}// Profile 用户资料模型
type Profile struct {BaseModelUserID uint `gorm:"uniqueIndex" json:"user_id"`FirstName string `gorm:"size:50" json:"first_name"`LastName string `gorm:"size:50" json:"last_name"`Bio string `gorm:"type:text" json:"bio"`Avatar string `gorm:"size:255" json:"avatar"`Phone string `gorm:"size:20" json:"phone"`Address string `gorm:"size:255" json:"address"`
}// Post 文章模型
type Post struct {BaseModelTitle string `gorm:"size:200;not null" json:"title"`Content string `gorm:"type:text" json:"content"`Published bool `gorm:"default:false" json:"published"`UserID uint `gorm:"not null" json:"user_id"`User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user,omitempty"`Tags []Tag `gorm:"many2many:post_tags;" json:"tags,omitempty"`Comments []Comment `gorm:"foreignKey:PostID" json:"comments,omitempty"`
}// Tag 标签模型
type Tag struct {BaseModelName string `gorm:"uniqueIndex;size:50;not null" json:"name"`Color string `gorm:"size:7;default:'#000000'" json:"color"` // 十六进制颜色Posts []Post `gorm:"many2many:post_tags;" json:"posts,omitempty"`
}// Comment 评论模型
type Comment struct {BaseModelContent string `gorm:"type:text;not null" json:"content"`PostID uint `gorm:"not null" json:"post_id"`Post Post `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"post,omitempty"`UserID uint `gorm:"not null" json:"user_id"`User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user,omitempty"`
}// Order 订单模型
type Order struct {BaseModelOrderNumber string `gorm:"uniqueIndex;size:50;not null" json:"order_number"`UserID uint `gorm:"not null" json:"user_id"`User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user,omitempty"`TotalAmount float64 `gorm:"type:decimal(10,2);not null" json:"total_amount"`Status string `gorm:"size:20;default:'pending'" json:"status"`Items []OrderItem `gorm:"foreignKey:OrderID" json:"items,omitempty"`
}// OrderItem 订单项模型
type OrderItem struct {BaseModelOrderID uint `gorm:"not null" json:"order_id"`Order Order `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"order,omitempty"`ProductID uint `gorm:"not null" json:"product_id"`Product Product `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"product,omitempty"`Quantity int `gorm:"not null;check:quantity > 0" json:"quantity"`Price float64 `gorm:"type:decimal(10,2);not null" json:"price"`Subtotal float64 `gorm:"type:decimal(10,2);not null" json:"subtotal"`
}// Product 商品模型
type Product struct {BaseModelName string `gorm:"size:100;not null" json:"name"`Description string `gorm:"type:text" json:"description"`Price float64 `gorm:"type:decimal(10,2);not null" json:"price"`Stock int `gorm:"not null;default:0" json:"stock"`IsActive bool `gorm:"default:true" json:"is_active"`CategoryID uint `gorm:"not null" json:"category_id"`Category Category `gorm:"constraint:OnUpdate:CASCADE,OnDelete:RESTRICT;" json:"category,omitempty"`OrderItems []OrderItem `gorm:"foreignKey:ProductID" json:"order_items,omitempty"`
}// Category 商品分类模型
type Category struct {BaseModelName string `gorm:"uniqueIndex;size:50;not null" json:"name"`Description string `gorm:"type:text" json:"description"`IsActive bool `gorm:"default:true" json:"is_active"`Products []Product `gorm:"foreignKey:CategoryID" json:"products,omitempty"`
}// TableName 方法用于自定义表名
func (User) TableName() string {return "users"
}func (Profile) TableName() string {return "user_profiles"
}func (Post) TableName() string {return "posts"
}func (Tag) TableName() string {return "tags"
}func (Comment) TableName() string {return "comments"
}func (Order) TableName() string {return "orders"
}func (OrderItem) TableName() string {return "order_items"
}func (Product) TableName() string {return "products"
}func (Category) TableName() string {return "categories"
}
3 数据库迁移
3.1 自动迁移
有了模型定义,接下来就是创建数据库表。GORM的AutoMigrate功能很方便,会根据模型自动创建表结构:
package migrationimport ("fmt""log""gorm.io/gorm""your-project/models"
)// AutoMigrate 自动迁移所有模型
func AutoMigrate(db *gorm.DB) error {log.Println("开始数据库迁移...")// 注意顺序,有外键关系的要先创建被引用的表err := db.AutoMigrate(&models.User{},&models.Profile{},&models.Category{},&models.Product{},&models.Post{},&models.Tag{},&models.Comment{},&models.Order{},&models.OrderItem{},)if err != nil {return fmt.Errorf("数据库迁移失败: %w", err)}log.Println("数据库迁移完成!")return nil
}// CreateIndexes 创建额外的索引
func CreateIndexes(db *gorm.DB) error {log.Println("创建额外索引...")indexes := []string{"CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)","CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)","CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id)","CREATE INDEX IF NOT EXISTS idx_posts_published ON posts(published)","CREATE INDEX IF NOT EXISTS idx_comments_post_id ON comments(post_id)","CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id)","CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status)","CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id)","CREATE INDEX IF NOT EXISTS idx_products_category_id ON products(category_id)","CREATE INDEX IF NOT EXISTS idx_products_is_active ON products(is_active)",}for _, indexSQL := range indexes {if err := db.Exec(indexSQL).Error; err != nil {log.Printf("创建索引失败: %s, 错误: %v", indexSQL, err)// 继续创建其他索引,不中断流程}}log.Println("索引创建完成!")return nil
}// DropTables 删除所有表(谨慎使用)
func DropTables(db *gorm.DB) error {log.Println("警告:正在删除所有表...")// 按相反的依赖顺序删除表tables := []interface{}{&models.OrderItem{},&models.Order{},&models.Comment{},&models.Tag{},&models.Post{},&models.Product{},&models.Category{},&models.Profile{},&models.User{},}for _, table := range tables {if err := db.Migrator().DropTable(table); err != nil {log.Printf("删除表失败: %v", err)}}log.Println("所有表已删除!")return nil
}// CheckMigrationStatus 检查迁移状态
func CheckMigrationStatus(db *gorm.DB) error {log.Println("检查数据库迁移状态...")models := []interface{}{&models.User{},&models.Profile{},&models.Category{},&models.Product{},&models.Post{},&models.Tag{},&models.Comment{},&models.Order{},&models.OrderItem{},}for _, model := range models {if db.Migrator().HasTable(model) {log.Printf("表 %T 存在", model)} else {log.Printf("表 %T 不存在", model)}}return nil
}
3.2 种子数据
开发时经常需要一些测试数据,手动插入太麻烦,写个种子数据脚本就方便多了。
package seedimport ("fmt""log""time""gorm.io/gorm""your-project/models"
)// SeedData 种子数据管理器
type SeedData struct {db *gorm.DB
}// NewSeedData 创建种子数据管理器
func NewSeedData(db *gorm.DB) *SeedData {return &SeedData{db: db}
}// SeedAll 一次性插入所有测试数据
func (s *SeedData) SeedAll() error {log.Println("开始准备测试数据...")if err := s.SeedCategories(); err != nil {return fmt.Errorf("分类数据插入失败: %w", err)}if err := s.SeedUsers(); err != nil {return fmt.Errorf("用户数据插入失败: %w", err)}if err := s.SeedProducts(); err != nil {return fmt.Errorf("商品数据插入失败: %w", err)}if err := s.SeedTags(); err != nil {return fmt.Errorf("标签数据插入失败: %w", err)}if err := s.SeedPosts(); err != nil {return fmt.Errorf("文章数据插入失败: %w", err)}log.Println("测试数据准备完毕!")return nil
}// SeedCategories 准备分类数据
func (s *SeedData) SeedCategories() error {categories := []models.Category{{Name: "电子产品", Description: "手机、电脑、数码配件", IsActive: true},{Name: "服装", Description: "男装女装、鞋帽配饰", IsActive: true},{Name: "图书", Description: "技术书籍、小说杂志", IsActive: true},{Name: "家居", Description: "家具装饰、生活用品", IsActive: true},{Name: "运动", Description: "健身器材、户外用品", IsActive: true},}for _, category := range categories {var existingCategory models.Categoryif err := s.db.Where("name = ?", category.Name).First(&existingCategory).Error; err != nil {if err == gorm.ErrRecordNotFound {if err := s.db.Create(&category).Error; err != nil {return fmt.Errorf("分类 %s 创建失败: %w", category.Name, err)}log.Printf("分类已创建: %s", category.Name)} else {return fmt.Errorf("分类查询出错: %w", err)}}}return nil
}// SeedUsers 准备用户数据
func (s *SeedData) SeedUsers() error {users := []models.User{{Username: "admin",Email: "admin@example.com",Password: "hashed_password_admin", // 实际项目中要用bcrypt加密Age: 30,IsActive: true,Profile: models.Profile{FirstName: "Admin",LastName: "User",Bio: "系统管理员",Phone: "13800138000",Address: "北京市朝阳区",},},{Username: "john_doe",Email: "john@example.com",Password: "hashed_password_john",Age: 25,IsActive: true,Profile: models.Profile{FirstName: "John",LastName: "Doe",Bio: "Go开发工程师",Phone: "13800138001",Address: "上海市浦东新区",},},{Username: "jane_smith",Email: "jane@example.com",Password: "hashed_password_jane",Age: 28,IsActive: true,Profile: models.Profile{FirstName: "Jane",LastName: "Smith",Bio: "产品经理",Phone: "13800138002",Address: "深圳市南山区",},},}for _, user := range users {var existingUser models.Userif err := s.db.Where("email = ?", user.Email).First(&existingUser).Error; err != nil {if err == gorm.ErrRecordNotFound {if err := s.db.Create(&user).Error; err != nil {return fmt.Errorf("用户 %s 创建失败: %w", user.Username, err)}log.Printf("用户已创建: %s", user.Username)} else {return fmt.Errorf("用户查询出错: %w", err)}}}return nil
}// SeedProducts 插入商品数据
func (s *SeedData) SeedProducts() error {// 先获取分类var categories []models.Categoryif err := s.db.Find(&categories).Error; err != nil {return fmt.Errorf("获取分类失败: %w", err)}if len(categories) == 0 {return fmt.Errorf("没有找到分类数据")}products := []models.Product{{Name: "iPhone 15 Pro",Description: "苹果最新款智能手机",Price: 8999.00,Stock: 100,IsActive: true,CategoryID: categories[0].ID, // 电子产品},{Name: "MacBook Pro",Description: "苹果专业级笔记本电脑",Price: 15999.00,Stock: 50,IsActive: true,CategoryID: categories[0].ID, // 电子产品},{Name: "Nike Air Max",Description: "耐克经典运动鞋",Price: 899.00,Stock: 200,IsActive: true,CategoryID: categories[1].ID, // 服装},{Name: "Go 语言编程",Description: "Go 语言学习指南",Price: 89.00,Stock: 500,IsActive: true,CategoryID: categories[2].ID, // 图书},}for _, product := range products {var existingProduct models.Productif err := s.db.Where("name = ?", product.Name).First(&existingProduct).Error; err != nil {if err == gorm.ErrRecordNotFound {if err := s.db.Create(&product).Error; err != nil {return fmt.Errorf("创建商品 %s 失败: %w", product.Name, err)}log.Printf("创建商品: %s", product.Name)} else {return fmt.Errorf("查询商品失败: %w", err)}}}return nil
}// SeedTags 插入标签数据
func (s *SeedData) SeedTags() error {tags := []models.Tag{{Name: "技术", Color: "#007bff"},{Name: "生活", Color: "#28a745"},{Name: "学习", Color: "#ffc107"},{Name: "工作", Color: "#dc3545"},{Name: "旅行", Color: "#17a2b8"},}for _, tag := range tags {var existingTag models.Tagif err := s.db.Where("name = ?", tag.Name).First(&existingTag).Error; err != nil {if err == gorm.ErrRecordNotFound {if err := s.db.Create(&tag).Error; err != nil {return fmt.Errorf("创建标签 %s 失败: %w", tag.Name, err)}log.Printf("创建标签: %s", tag.Name)} else {return fmt.Errorf("查询标签失败: %w", err)}}}return nil
}// SeedPosts 插入文章数据
func (s *SeedData) SeedPosts() error {// 获取用户和标签var users []models.Userif err := s.db.Find(&users).Error; err != nil {return fmt.Errorf("获取用户失败: %w", err)}var tags []models.Tagif err := s.db.Find(&tags).Error; err != nil {return fmt.Errorf("获取标签失败: %w", err)}if len(users) == 0 || len(tags) == 0 {return fmt.Errorf("缺少用户或标签数据")}posts := []models.Post{{Title: "Go 语言入门指南",Content: "这是一篇关于 Go 语言基础知识的文章...",Published: true,UserID: users[0].ID,},{Title: "GORM 使用技巧",Content: "分享一些 GORM 的实用技巧和最佳实践...",Published: true,UserID: users[1].ID,},{Title: "Web 开发最佳实践",Content: "总结 Web 开发中的一些最佳实践...",Published: false,UserID: users[2].ID,},}for i, post := range posts {var existingPost models.Postif err := s.db.Where("title = ?", post.Title).First(&existingPost).Error; err != nil {if err == gorm.ErrRecordNotFound {if err := s.db.Create(&post).Error; err != nil {return fmt.Errorf("创建文章 %s 失败: %w", post.Title, err)}// 为文章添加标签if i < len(tags) {if err := s.db.Model(&post).Association("Tags").Append(&tags[i]); err != nil {log.Printf("为文章添加标签失败: %v", err)}}log.Printf("创建文章: %s", post.Title)} else {return fmt.Errorf("查询文章失败: %w", err)}}}return nil
}
4. CRUD 操作
有了模型和数据库连接,接下来就是实际的增删改查操作了。这里用仓库模式来组织代码,让业务逻辑更清晰。
4.1 创建(Create)
package repositoryimport ("fmt""time""gorm.io/gorm""your-project/models"
)// UserRepository 用户数据仓库
type UserRepository struct {db *gorm.DB
}// NewUserRepository 创建用户仓库实例
func NewUserRepository(db *gorm.DB) *UserRepository {return &UserRepository{db: db}
}// CreateUser 创建新用户
func (r *UserRepository) CreateUser(user *models.User) error {// 先检查用户名和邮箱是否重复var existingUser models.Userif err := r.db.Where("username = ? OR email = ?", user.Username, user.Email).First(&existingUser).Error; err == nil {return fmt.Errorf("用户名或邮箱已被使用")} else if err != gorm.ErrRecordNotFound {return fmt.Errorf("用户检查失败: %w", err)}// 保存到数据库if err := r.db.Create(user).Error; err != nil {return fmt.Errorf("用户创建失败: %w", err)}return nil
}// CreateUserWithProfile 创建用户和个人资料
func (r *UserRepository) CreateUserWithProfile(user *models.User, profile *models.Profile) error {// 用事务保证数据一致性,要么都成功要么都失败return r.db.Transaction(func(tx *gorm.DB) error {// 先创建用户if err := tx.Create(user).Error; err != nil {return fmt.Errorf("用户创建失败: %w", err)}// 关联用户IDprofile.UserID = user.ID// 再创建个人资料if err := tx.Create(profile).Error; err != nil {return fmt.Errorf("创建用户资料失败: %w", err)}return nil})
}// BatchCreateUsers 批量创建用户
func (r *UserRepository) BatchCreateUsers(users []models.User) error {// 使用批量插入提高性能if err := r.db.CreateInBatches(users, 100).Error; err != nil {return fmt.Errorf("批量创建用户失败: %w", err)}return nil
}// CreatePost 创建文章
func (r *UserRepository) CreatePost(post *models.Post, tagIDs []uint) error {return r.db.Transaction(func(tx *gorm.DB) error {// 创建文章if err := tx.Create(post).Error; err != nil {return fmt.Errorf("创建文章失败: %w", err)}// 关联标签if len(tagIDs) > 0 {var tags []models.Tagif err := tx.Where("id IN ?", tagIDs).Find(&tags).Error; err != nil {return fmt.Errorf("查找标签失败: %w", err)}if err := tx.Model(post).Association("Tags").Append(tags); err != nil {return fmt.Errorf("关联标签失败: %w", err)}}return nil})
}// CreateOrder 创建订单
func (r *UserRepository) CreateOrder(order *models.Order, items []models.OrderItem) error {return r.db.Transaction(func(tx *gorm.DB) error {// 创建订单if err := tx.Create(order).Error; err != nil {return fmt.Errorf("创建订单失败: %w", err)}// 设置订单项的订单ID并计算小计var totalAmount float64for i := range items {items[i].OrderID = order.IDitems[i].Subtotal = items[i].Price * float64(items[i].Quantity)totalAmount += items[i].Subtotal}// 批量创建订单项if err := tx.CreateInBatches(items, 100).Error; err != nil {return fmt.Errorf("创建订单项失败: %w", err)}// 更新订单总金额if err := tx.Model(order).Update("total_amount", totalAmount).Error; err != nil {return fmt.Errorf("更新订单总金额失败: %w", err)}// 更新商品库存for _, item := range items {if err := tx.Model(&models.Product{}).Where("id = ?", item.ProductID).Update("stock", gorm.Expr("stock - ?", item.Quantity)).Error; err != nil {return fmt.Errorf("更新商品库存失败: %w", err)}}return nil})
}
4.2 查询(Read)
// GetUserByID 根据ID获取用户
func (r *UserRepository) GetUserByID(id uint) (*models.User, error) {var user models.Userif err := r.db.First(&user, id).Error; err != nil {if err == gorm.ErrRecordNotFound {return nil, fmt.Errorf("用户不存在")}return nil, fmt.Errorf("查询用户失败: %w", err)}return &user, nil
}// GetUserWithProfile 获取用户及其资料
func (r *UserRepository) GetUserWithProfile(id uint) (*models.User, error) {var user models.Userif err := r.db.Preload("Profile").First(&user, id).Error; err != nil {if err == gorm.ErrRecordNotFound {return nil, fmt.Errorf("用户不存在")}return nil, fmt.Errorf("查询用户失败: %w", err)}return &user, nil
}// GetUserWithPosts 获取用户及其文章
func (r *UserRepository) GetUserWithPosts(id uint) (*models.User, error) {var user models.Userif err := r.db.Preload("Posts").Preload("Posts.Tags").First(&user, id).Error; err != nil {if err == gorm.ErrRecordNotFound {return nil, fmt.Errorf("用户不存在")}return nil, fmt.Errorf("查询用户失败: %w", err)}return &user, nil
}// GetUsers 分页获取用户列表
func (r *UserRepository) GetUsers(page, pageSize int) ([]models.User, int64, error) {var users []models.Uservar total int64// 计算总数if err := r.db.Model(&models.User{}).Count(&total).Error; err != nil {return nil, 0, fmt.Errorf("计算用户总数失败: %w", err)}// 分页查询offset := (page - 1) * pageSizeif err := r.db.Offset(offset).Limit(pageSize).Find(&users).Error; err != nil {return nil, 0, fmt.Errorf("查询用户列表失败: %w", err)}return users, total, nil
}// SearchUsers 搜索用户
func (r *UserRepository) SearchUsers(keyword string, page, pageSize int) ([]models.User, int64, error) {var users []models.Uservar total int64query := r.db.Model(&models.User{}).Where("username ILIKE ? OR email ILIKE ?", "%"+keyword+"%", "%"+keyword+"%")// 计算总数if err := query.Count(&total).Error; err != nil {return nil, 0, fmt.Errorf("计算搜索结果总数失败: %w", err)}// 分页查询offset := (page - 1) * pageSizeif err := query.Offset(offset).Limit(pageSize).Find(&users).Error; err != nil {return nil, 0, fmt.Errorf("搜索用户失败: %w", err)}return users, total, nil
}// GetActiveUsers 获取活跃用户
func (r *UserRepository) GetActiveUsers() ([]models.User, error) {var users []models.Userif err := r.db.Where("is_active = ?", true).Find(&users).Error; err != nil {return nil, fmt.Errorf("查询活跃用户失败: %w", err)}return users, nil
}// GetUsersByAge 根据年龄范围获取用户
func (r *UserRepository) GetUsersByAge(minAge, maxAge int) ([]models.User, error) {var users []models.Userif err := r.db.Where("age BETWEEN ? AND ?", minAge, maxAge).Find(&users).Error; err != nil {return nil, fmt.Errorf("根据年龄查询用户失败: %w", err)}return users, nil
}// GetPostsWithComments 获取文章及其评论
func (r *UserRepository) GetPostsWithComments(userID uint) ([]models.Post, error) {var posts []models.Postif err := r.db.Where("user_id = ?", userID).Preload("Comments").Preload("Comments.User").Preload("Tags").Find(&posts).Error; err != nil {return nil, fmt.Errorf("查询用户文章失败: %w", err)}return posts, nil
}// GetOrderWithItems 获取订单及其订单项
func (r *UserRepository) GetOrderWithItems(orderID uint) (*models.Order, error) {var order models.Orderif err := r.db.Preload("Items").Preload("Items.Product").Preload("User").First(&order, orderID).Error; err != nil {if err == gorm.ErrRecordNotFound {return nil, fmt.Errorf("订单不存在")}return nil, fmt.Errorf("查询订单失败: %w", err)}return &order, nil
}// GetUserOrders 获取用户订单
func (r *UserRepository) GetUserOrders(userID uint, status string) ([]models.Order, error) {query := r.db.Where("user_id = ?", userID)if status != "" {query = query.Where("status = ?", status)}var orders []models.Orderif err := query.Preload("Items").Preload("Items.Product").Order("created_at DESC").Find(&orders).Error; err != nil {return nil, fmt.Errorf("查询用户订单失败: %w", err)}return orders, nil
}// GetPopularPosts 获取热门文章
func (r *UserRepository) GetPopularPosts(limit int) ([]models.Post, error) {var posts []models.Postif err := r.db.Where("published = ?", true).Preload("User").Preload("Tags").Order("created_at DESC").Limit(limit).Find(&posts).Error; err != nil {return nil, fmt.Errorf("查询热门文章失败: %w", err)}return posts, nil
}// GetUserStatistics 获取用户统计信息
func (r *UserRepository) GetUserStatistics(userID uint) (map[string]interface{}, error) {stats := make(map[string]interface{})// 文章数量var postCount int64if err := r.db.Model(&models.Post{}).Where("user_id = ?", userID).Count(&postCount).Error; err != nil {return nil, fmt.Errorf("统计文章数量失败: %w", err)}stats["post_count"] = postCount// 已发布文章数量var publishedPostCount int64if err := r.db.Model(&models.Post{}).Where("user_id = ? AND published = ?", userID, true).Count(&publishedPostCount).Error; err != nil {return nil, fmt.Errorf("统计已发布文章数量失败: %w", err)}stats["published_post_count"] = publishedPostCount// 订单数量var orderCount int64if err := r.db.Model(&models.Order{}).Where("user_id = ?", userID).Count(&orderCount).Error; err != nil {return nil, fmt.Errorf("统计订单数量失败: %w", err)}stats["order_count"] = orderCount// 订单总金额var totalAmount float64if err := r.db.Model(&models.Order{}).Where("user_id = ?", userID).Select("COALESCE(SUM(total_amount), 0)").Scan(&totalAmount).Error; err != nil {return nil, fmt.Errorf("统计订单总金额失败: %w", err)}stats["total_amount"] = totalAmountreturn stats, nil
}
4.3 更新(Update)
// UpdateUser 更新用户信息
func (r *UserRepository) UpdateUser(id uint, updates map[string]interface{}) error {// 检查用户是否存在var user models.Userif err := r.db.First(&user, id).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("用户不存在")}return fmt.Errorf("查询用户失败: %w", err)}// 如果更新用户名或邮箱,需要检查唯一性if username, ok := updates["username"]; ok {var existingUser models.Userif err := r.db.Where("username = ? AND id != ?", username, id).First(&existingUser).Error; err == nil {return fmt.Errorf("用户名已存在")} else if err != gorm.ErrRecordNotFound {return fmt.Errorf("检查用户名失败: %w", err)}}if email, ok := updates["email"]; ok {var existingUser models.Userif err := r.db.Where("email = ? AND id != ?", email, id).First(&existingUser).Error; err == nil {return fmt.Errorf("邮箱已存在")} else if err != gorm.ErrRecordNotFound {return fmt.Errorf("检查邮箱失败: %w", err)}}// 更新用户信息if err := r.db.Model(&user).Updates(updates).Error; err != nil {return fmt.Errorf("更新用户失败: %w", err)}return nil
}// UpdateUserProfile 更新用户资料
func (r *UserRepository) UpdateUserProfile(userID uint, profile *models.Profile) error {return r.db.Transaction(func(tx *gorm.DB) error {// 检查用户是否存在var user models.Userif err := tx.First(&user, userID).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("用户不存在")}return fmt.Errorf("查询用户失败: %w", err)}// 查找现有资料var existingProfile models.Profileif err := tx.Where("user_id = ?", userID).First(&existingProfile).Error; err != nil {if err == gorm.ErrRecordNotFound {// 创建新资料profile.UserID = userIDif err := tx.Create(profile).Error; err != nil {return fmt.Errorf("创建用户资料失败: %w", err)}} else {return fmt.Errorf("查询用户资料失败: %w", err)}} else {// 更新现有资料if err := tx.Model(&existingProfile).Updates(profile).Error; err != nil {return fmt.Errorf("更新用户资料失败: %w", err)}}return nil})
}// UpdatePost 更新文章
func (r *UserRepository) UpdatePost(postID uint, updates map[string]interface{}, tagIDs []uint) error {return r.db.Transaction(func(tx *gorm.DB) error {// 检查文章是否存在var post models.Postif err := tx.First(&post, postID).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("文章不存在")}return fmt.Errorf("查询文章失败: %w", err)}// 更新文章信息if err := tx.Model(&post).Updates(updates).Error; err != nil {return fmt.Errorf("更新文章失败: %w", err)}// 更新标签关联if tagIDs != nil {// 清除现有标签关联if err := tx.Model(&post).Association("Tags").Clear(); err != nil {return fmt.Errorf("清除文章标签失败: %w", err)}// 添加新的标签关联if len(tagIDs) > 0 {var tags []models.Tagif err := tx.Where("id IN ?", tagIDs).Find(&tags).Error; err != nil {return fmt.Errorf("查找标签失败: %w", err)}if err := tx.Model(&post).Association("Tags").Append(tags); err != nil {return fmt.Errorf("关联标签失败: %w", err)}}}return nil})
}// UpdateOrderStatus 更新订单状态
func (r *UserRepository) UpdateOrderStatus(orderID uint, status string) error {// 检查订单是否存在var order models.Orderif err := r.db.First(&order, orderID).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("订单不存在")}return fmt.Errorf("查询订单失败: %w", err)}// 更新订单状态if err := r.db.Model(&order).Update("status", status).Error; err != nil {return fmt.Errorf("更新订单状态失败: %w", err)}return nil
}// UpdateProductStock 更新商品库存
func (r *UserRepository) UpdateProductStock(productID uint, quantity int) error {// 检查商品是否存在var product models.Productif err := r.db.First(&product, productID).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("商品不存在")}return fmt.Errorf("查询商品失败: %w", err)}// 更新库存if err := r.db.Model(&product).Update("stock", gorm.Expr("stock + ?", quantity)).Error; err != nil {return fmt.Errorf("更新商品库存失败: %w", err)}return nil
}// BatchUpdateUsers 批量更新用户
func (r *UserRepository) BatchUpdateUsers(userIDs []uint, updates map[string]interface{}) error {if len(userIDs) == 0 {return nil}if err := r.db.Model(&models.User{}).Where("id IN ?", userIDs).Updates(updates).Error; err != nil {return fmt.Errorf("批量更新用户失败: %w", err)}return nil
}// IncrementUserAge 增加用户年龄
func (r *UserRepository) IncrementUserAge(userID uint, increment int) error {if err := r.db.Model(&models.User{}).Where("id = ?", userID).Update("age", gorm.Expr("age + ?", increment)).Error; err != nil {return fmt.Errorf("增加用户年龄失败: %w", err)}return nil
}// ToggleUserStatus 切换用户状态
func (r *UserRepository) ToggleUserStatus(userID uint) error {if err := r.db.Model(&models.User{}).Where("id = ?", userID).Update("is_active", gorm.Expr("NOT is_active")).Error; err != nil {return fmt.Errorf("切换用户状态失败: %w", err)}return nil
}
4.4 删除(Delete)
删除操作要特别小心,GORM默认是软删除,数据还在只是标记了删除时间。
// DeleteUser 删除用户(软删除)
func (r *UserRepository) DeleteUser(id uint) error {// 先确认用户存在var user models.Userif err := r.db.First(&user, id).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("用户不存在")}return fmt.Errorf("用户查询失败: %w", err)}// 软删除,数据还在只是标记删除时间if err := r.db.Delete(&user).Error; err != nil {return fmt.Errorf("用户删除失败: %w", err)}return nil
}// HardDeleteUser 彻底删除用户
func (r *UserRepository) HardDeleteUser(id uint) error {return r.db.Transaction(func(tx *gorm.DB) error {// 先确认用户存在(包括软删除的)var user models.Userif err := tx.Unscoped().First(&user, id).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("用户不存在")}return fmt.Errorf("用户查询失败: %w", err)}// 清理用户资料if err := tx.Unscoped().Where("user_id = ?", id).Delete(&models.Profile{}).Error; err != nil {return fmt.Errorf("用户资料删除失败: %w", err)}// 清理用户评论if err := tx.Unscoped().Where("user_id = ?", id).Delete(&models.Comment{}).Error; err != nil {return fmt.Errorf("用户评论删除失败: %w", err)}// 删除用户的文章(包括标签关联)var posts []models.Postif err := tx.Where("user_id = ?", id).Find(&posts).Error; err != nil {return fmt.Errorf("查询用户文章失败: %w", err)}for _, post := range posts {// 清除文章标签关联if err := tx.Model(&post).Association("Tags").Clear(); err != nil {return fmt.Errorf("清除文章标签关联失败: %w", err)}}// 删除用户的文章if err := tx.Unscoped().Where("user_id = ?", id).Delete(&models.Post{}).Error; err != nil {return fmt.Errorf("删除用户文章失败: %w", err)}// 删除用户的订单项if err := tx.Unscoped().Where("order_id IN (SELECT id FROM orders WHERE user_id = ?)", id).Delete(&models.OrderItem{}).Error; err != nil {return fmt.Errorf("删除用户订单项失败: %w", err)}// 删除用户的订单if err := tx.Unscoped().Where("user_id = ?", id).Delete(&models.Order{}).Error; err != nil {return fmt.Errorf("删除用户订单失败: %w", err)}// 最后删除用户if err := tx.Unscoped().Delete(&user).Error; err != nil {return fmt.Errorf("删除用户失败: %w", err)}return nil})
}// DeletePost 删除文章
func (r *UserRepository) DeletePost(postID uint) error {return r.db.Transaction(func(tx *gorm.DB) error {// 检查文章是否存在var post models.Postif err := tx.First(&post, postID).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("文章不存在")}return fmt.Errorf("查询文章失败: %w", err)}// 清除标签关联if err := tx.Model(&post).Association("Tags").Clear(); err != nil {return fmt.Errorf("清除文章标签关联失败: %w", err)}// 删除文章评论if err := tx.Where("post_id = ?", postID).Delete(&models.Comment{}).Error; err != nil {return fmt.Errorf("删除文章评论失败: %w", err)}// 删除文章if err := tx.Delete(&post).Error; err != nil {return fmt.Errorf("删除文章失败: %w", err)}return nil})
}// DeleteOrder 删除订单
func (r *UserRepository) DeleteOrder(orderID uint) error {return r.db.Transaction(func(tx *gorm.DB) error {// 检查订单是否存在var order models.Orderif err := tx.First(&order, orderID).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("订单不存在")}return fmt.Errorf("查询订单失败: %w", err)}// 获取订单项以恢复库存var orderItems []models.OrderItemif err := tx.Where("order_id = ?", orderID).Find(&orderItems).Error; err != nil {return fmt.Errorf("查询订单项失败: %w", err)}// 恢复商品库存for _, item := range orderItems {if err := tx.Model(&models.Product{}).Where("id = ?", item.ProductID).Update("stock", gorm.Expr("stock + ?", item.Quantity)).Error; err != nil {return fmt.Errorf("恢复商品库存失败: %w", err)}}// 删除订单项if err := tx.Where("order_id = ?", orderID).Delete(&models.OrderItem{}).Error; err != nil {return fmt.Errorf("删除订单项失败: %w", err)}// 删除订单if err := tx.Delete(&order).Error; err != nil {return fmt.Errorf("删除订单失败: %w", err)}return nil})
}// BatchDeleteUsers 批量删除用户
func (r *UserRepository) BatchDeleteUsers(userIDs []uint) error {if len(userIDs) == 0 {return nil}if err := r.db.Where("id IN ?", userIDs).Delete(&models.User{}).Error; err != nil {return fmt.Errorf("批量删除用户失败: %w", err)}return nil
}// DeleteExpiredUsers 删除过期用户
func (r *UserRepository) DeleteExpiredUsers(days int) error {expiredDate := time.Now().AddDate(0, 0, -days)if err := r.db.Where("created_at < ? AND is_active = ?", expiredDate, false).Delete(&models.User{}).Error; err != nil {return fmt.Errorf("删除过期用户失败: %w", err)}return nil
}// RestoreUser 恢复软删除的用户
func (r *UserRepository) RestoreUser(id uint) error {var user models.Userif err := r.db.Unscoped().Where("id = ?", id).First(&user).Error; err != nil {if err == gorm.ErrRecordNotFound {return fmt.Errorf("用户不存在")}return fmt.Errorf("查询用户失败: %w", err)}if user.DeletedAt.Valid {if err := r.db.Unscoped().Model(&user).Update("deleted_at", nil).Error; err != nil {return fmt.Errorf("恢复用户失败: %w", err)}}return nil
}// GetDeletedUsers 获取已删除的用户
func (r *UserRepository) GetDeletedUsers() ([]models.User, error) {var users []models.Userif err := r.db.Unscoped().Where("deleted_at IS NOT NULL").Find(&users).Error; err != nil {return nil, fmt.Errorf("查询已删除用户失败: %w", err)}return users, nil
}
5. 高级查询
基础的增删改查掌握后,就要学习一些复杂查询了。这些在实际项目中经常用到。
5.1 复杂查询
package advancedimport ("fmt""time""gorm.io/gorm""your-project/models"
)// AdvancedQuery 高级查询工具
type AdvancedQuery struct {db *gorm.DB
}// NewAdvancedQuery 创建高级查询工具
func NewAdvancedQuery(db *gorm.DB) *AdvancedQuery {return &AdvancedQuery{db: db}
}// GetUsersByComplexConditions 多条件组合查询用户
func (aq *AdvancedQuery) GetUsersByComplexConditions(minAge, maxAge int,isActive *bool,emailDomain string,hasProfile bool,createdAfter *time.Time,
) ([]models.User, error) {query := aq.db.Model(&models.User{})// 年龄范围筛选if minAge > 0 || maxAge > 0 {if minAge > 0 && maxAge > 0 {query = query.Where("age BETWEEN ? AND ?", minAge, maxAge)} else if minAge > 0 {query = query.Where("age >= ?", minAge)} else {query = query.Where("age <= ?", maxAge)}}// 活跃状态筛选if isActive != nil {query = query.Where("is_active = ?", *isActive)}// 邮箱域名筛选if emailDomain != "" {query = query.Where("email LIKE ?", "%@"+emailDomain)}// 是否有个人资料if hasProfile {query = query.Joins("JOIN user_profiles ON users.id = user_profiles.user_id")}// 注册时间筛选if createdAfter != nil {query = query.Where("users.created_at > ?", *createdAfter)}var users []models.Userif err := query.Find(&users).Error; err != nil {return nil, fmt.Errorf("多条件查询用户失败: %w", err)}return users, nil
}// GetPostsWithStatistics 获取文章及统计信息
func (aq *AdvancedQuery) GetPostsWithStatistics() ([]map[string]interface{}, error) {var results []map[string]interface{}rows, err := aq.db.Table("posts").Select(`posts.id,posts.title,posts.published,posts.created_at,users.username as author,COUNT(DISTINCT comments.id) as comment_count,COUNT(DISTINCT post_tags.tag_id) as tag_count`).Joins("LEFT JOIN users ON posts.user_id = users.id").Joins("LEFT JOIN comments ON posts.id = comments.post_id").Joins("LEFT JOIN post_tags ON posts.id = post_tags.post_id").Group("posts.id, posts.title, posts.published, posts.created_at, users.username").Rows()if err != nil {return nil, fmt.Errorf("查询文章统计失败: %w", err)}defer rows.Close()for rows.Next() {var id uintvar title, author stringvar published boolvar createdAt time.Timevar commentCount, tagCount intif err := rows.Scan(&id, &title, &published, &createdAt, &author, &commentCount, &tagCount); err != nil {return nil, fmt.Errorf("扫描文章统计数据失败: %w", err)}results = append(results, map[string]interface{}{"id": id,"title": title,"published": published,"created_at": createdAt,"author": author,"comment_count": commentCount,"tag_count": tagCount,})}return results, nil
}
5.2 子查询和关联查询
// GetUsersWithSubquery 使用子查询获取用户
func (aq *AdvancedQuery) GetUsersWithSubquery() ([]models.User, error) {// 获取有文章的用户subQuery := aq.db.Model(&models.Post{}).Select("DISTINCT user_id").Where("published = ?", true)var users []models.Userif err := aq.db.Where("id IN (?)", subQuery).Find(&users).Error; err != nil {return nil, fmt.Errorf("子查询获取用户失败: %w", err)}return users, nil
}// GetProductsWithLowStock 获取库存不足的商品
func (aq *AdvancedQuery) GetProductsWithLowStock(threshold int) ([]models.Product, error) {var products []models.Productif err := aq.db.Where("stock < ?", threshold).Preload("Category").Find(&products).Error; err != nil {return nil, fmt.Errorf("查询库存不足商品失败: %w", err)}return products, nil
}// GetUsersWithOrderStats 获取用户及其订单统计
func (aq *AdvancedQuery) GetUsersWithOrderStats() ([]map[string]interface{}, error) {var results []map[string]interface{}rows, err := aq.db.Table("users").Select(`users.id,users.username,users.email,COUNT(orders.id) as order_count,COALESCE(SUM(orders.total_amount), 0) as total_spent,COALESCE(AVG(orders.total_amount), 0) as avg_order_amount,MAX(orders.created_at) as last_order_date`).Joins("LEFT JOIN orders ON users.id = orders.user_id").Group("users.id, users.username, users.email").Rows()if err != nil {return nil, fmt.Errorf("查询用户订单统计失败: %w", err)}defer rows.Close()for rows.Next() {var id uintvar username, email stringvar orderCount intvar totalSpent, avgOrderAmount float64var lastOrderDate *time.Timeif err := rows.Scan(&id, &username, &email, &orderCount, &totalSpent, &avgOrderAmount, &lastOrderDate); err != nil {return nil, fmt.Errorf("扫描用户订单统计失败: %w", err)}results = append(results, map[string]interface{}{"id": id,"username": username,"email": email,"order_count": orderCount,"total_spent": totalSpent,"avg_order_amount": avgOrderAmount,"last_order_date": lastOrderDate,})}return results, nil
}
6. 性能优化
6.1 预加载优化
package optimizationimport ("fmt""gorm.io/gorm""your-project/models"
)// OptimizedQuery 优化查询服务
type OptimizedQuery struct {db *gorm.DB
}// NewOptimizedQuery 创建优化查询服务
func NewOptimizedQuery(db *gorm.DB) *OptimizedQuery {return &OptimizedQuery{db: db}
}// GetUsersWithOptimizedPreload 优化的预加载查询
func (oq *OptimizedQuery) GetUsersWithOptimizedPreload() ([]models.User, error) {var users []models.User// 使用选择性预加载,只加载需要的字段if err := oq.db.Preload("Profile", func(db *gorm.DB) *gorm.DB {return db.Select("user_id, first_name, last_name, bio")}).Preload("Posts", func(db *gorm.DB) *gorm.DB {return db.Select("id, title, published, user_id").Where("published = ?", true)}).Preload("Posts.Tags", func(db *gorm.DB) *gorm.DB {return db.Select("id, name, color")}).Select("id, username, email, is_active").Find(&users).Error; err != nil {return nil, fmt.Errorf("优化预加载查询失败: %w", err)}return users, nil
}// GetPostsWithLimitedPreload 限制预加载数量
func (oq *OptimizedQuery) GetPostsWithLimitedPreload() ([]models.Post, error) {var posts []models.Postif err := oq.db.Preload("User", func(db *gorm.DB) *gorm.DB {return db.Select("id, username")}).Preload("Comments", func(db *gorm.DB) *gorm.DB {return db.Select("id, content, post_id, user_id").Limit(5).Order("created_at DESC")}).Preload("Comments.User", func(db *gorm.DB) *gorm.DB {return db.Select("id, username")}).Preload("Tags", func(db *gorm.DB) *gorm.DB {return db.Select("id, name, color")}).Where("published = ?", true).Find(&posts).Error; err != nil {return nil, fmt.Errorf("限制预加载查询失败: %w", err)}return posts, nil
}// GetOrdersWithCustomPreload 自定义预加载条件
func (oq *OptimizedQuery) GetOrdersWithCustomPreload(userID uint) ([]models.Order, error) {var orders []models.Orderif err := oq.db.Preload("User", func(db *gorm.DB) *gorm.DB {return db.Select("id, username, email")}).Preload("Items", func(db *gorm.DB) *gorm.DB {return db.Select("id, order_id, product_id, quantity, price, subtotal")}).Preload("Items.Product", func(db *gorm.DB) *gorm.DB {return db.Select("id, name, price").Where("is_active = ?", true)}).Where("user_id = ?", userID).Order("created_at DESC").Find(&orders).Error; err != nil {return nil, fmt.Errorf("自定义预加载查询失败: %w", err)}return orders, nil
}
6.2 批量操作优化
// BatchCreateOptimized 优化的批量创建
func (oq *OptimizedQuery) BatchCreateOptimized(users []models.User) error {// 使用事务和批量插入return oq.db.Transaction(func(tx *gorm.DB) error {// 分批插入,每批 1000 条记录batchSize := 1000for i := 0; i < len(users); i += batchSize {end := i + batchSizeif end > len(users) {end = len(users)}if err := tx.CreateInBatches(users[i:end], batchSize).Error; err != nil {return fmt.Errorf("批量创建用户失败: %w", err)}}return nil})
}// BatchUpdateOptimized 优化的批量更新
func (oq *OptimizedQuery) BatchUpdateOptimized(updates []map[string]interface{}) error {return oq.db.Transaction(func(tx *gorm.DB) error {for _, update := range updates {id := update["id"]delete(update, "id")if err := tx.Model(&models.User{}).Where("id = ?", id).Updates(update).Error; err != nil {return fmt.Errorf("批量更新用户失败: %w", err)}}return nil})
}// UpsertUsers UPSERT 操作(插入或更新)
func (oq *OptimizedQuery) UpsertUsers(users []models.User) error {return oq.db.Transaction(func(tx *gorm.DB) error {for _, user := range users {var existingUser models.Userif err := tx.Where("email = ?", user.Email).First(&existingUser).Error; err != nil {if err == gorm.ErrRecordNotFound {// 插入新用户if err := tx.Create(&user).Error; err != nil {return fmt.Errorf("插入用户失败: %w", err)}} else {return fmt.Errorf("查询用户失败: %w", err)}} else {// 更新现有用户if err := tx.Model(&existingUser).Updates(&user).Error; err != nil {return fmt.Errorf("更新用户失败: %w", err)}}}return nil})
}
6.3 索引优化
// CreateOptimalIndexes 创建优化索引
func (oq *OptimizedQuery) CreateOptimalIndexes() error {indexes := []string{// 复合索引"CREATE INDEX IF NOT EXISTS idx_users_email_active ON users(email, is_active)","CREATE INDEX IF NOT EXISTS idx_posts_user_published ON posts(user_id, published)","CREATE INDEX IF NOT EXISTS idx_orders_user_status ON orders(user_id, status)",// 部分索引"CREATE INDEX IF NOT EXISTS idx_posts_published_true ON posts(created_at) WHERE published = true","CREATE INDEX IF NOT EXISTS idx_users_active_true ON users(created_at) WHERE is_active = true",// 表达式索引"CREATE INDEX IF NOT EXISTS idx_users_email_domain ON users((split_part(email, '@', 2)))","CREATE INDEX IF NOT EXISTS idx_orders_year ON orders(EXTRACT(YEAR FROM created_at))",// 全文搜索索引(PostgreSQL)"CREATE INDEX IF NOT EXISTS idx_posts_fulltext ON posts USING gin(to_tsvector('english', title || ' ' || content))",}for _, indexSQL := range indexes {if err := oq.db.Exec(indexSQL).Error; err != nil {// 记录错误但继续创建其他索引fmt.Printf("创建索引失败: %s, 错误: %v\n", indexSQL, err)}}return nil
}// AnalyzeQueryPerformance 分析查询性能
func (oq *OptimizedQuery) AnalyzeQueryPerformance(query string, args ...interface{}) error {// 执行 EXPLAIN ANALYZEexplainQuery := "EXPLAIN ANALYZE " + queryrows, err := oq.db.Raw(explainQuery, args...).Rows()if err != nil {return fmt.Errorf("执行查询分析失败: %w", err)}defer rows.Close()fmt.Println("查询执行计划:")for rows.Next() {var plan stringif err := rows.Scan(&plan); err != nil {return fmt.Errorf("扫描执行计划失败: %w", err)}fmt.Println(plan)}return nil
}
7. 完整示例
7.1 主程序
package mainimport ("fmt""log""time""gorm.io/gorm""your-project/models""your-project/migration""your-project/seed""your-project/repository""your-project/advanced""your-project/optimization"
)func main() {// 连接数据库db, err := ConnectSQLite("gorm_example.db")if err != nil {log.Fatal("数据库连接失败:", err)}// 配置连接池if err := ConfigureConnectionPool(db); err != nil {log.Fatal("配置连接池失败:", err)}// 数据库迁移migrator := migration.NewSeedData(db)if err := migration.AutoMigrate(db); err != nil {log.Fatal("数据库迁移失败:", err)}// 创建索引if err := migration.CreateIndexes(db); err != nil {log.Fatal("创建索引失败:", err)}// 插入种子数据seeder := seed.NewSeedData(db)if err := seeder.SeedAll(); err != nil {log.Fatal("插入种子数据失败:", err)}// 演示 CRUD 操作demonstrateCRUD(db)// 演示高级查询demonstrateAdvancedQueries(db)// 演示性能优化demonstrateOptimization(db)fmt.Println("GORM 示例程序执行完成!")
}// demonstrateCRUD 演示 CRUD 操作
func demonstrateCRUD(db *gorm.DB) {fmt.Println("\n=== CRUD 操作演示 ===")userRepo := repository.NewUserRepository(db)// 创建用户newUser := &models.User{Username: "test_user",Email: "test@example.com",Password: "hashed_password",Age: 25,IsActive: true,}if err := userRepo.CreateUser(newUser); err != nil {log.Printf("创建用户失败: %v", err)} else {fmt.Printf("创建用户成功: ID=%d\n", newUser.ID)}// 查询用户user, err := userRepo.GetUserByID(newUser.ID)if err != nil {log.Printf("查询用户失败: %v", err)} else {fmt.Printf("查询用户成功: %s (%s)\n", user.Username, user.Email)}// 更新用户信息updates := map[string]interface{}{"age": 26,"is_active": false,}if err := userRepo.UpdateUser(newUser.ID, updates); err != nil {log.Printf("用户更新失败: %v", err)} else {fmt.Println("用户更新成功")}// 分页查询用户列表users, total, err := userRepo.GetUsers(1, 10)if err != nil {log.Printf("分页查询失败: %v", err)} else {fmt.Printf("分页查询成功: 总数=%d, 当前页=%d条\n", total, len(users))}// 删除用户(软删除)if err := userRepo.DeleteUser(newUser.ID); err != nil {log.Printf("用户删除失败: %v", err)} else {fmt.Println("用户删除成功")}
}// demonstrateAdvancedQueries 演示高级查询功能
func demonstrateAdvancedQueries(db *gorm.DB) {fmt.Println("\n=== 高级查询演示 ===")advancedQuery := advanced.NewAdvancedQuery(db)// 多条件组合查询isActive := truecreatedAfter := time.Now().AddDate(0, 0, -30) // 最近30天注册的用户users, err := advancedQuery.GetUsersByComplexConditions(18, 65, // 年龄范围&isActive, // 活跃状态"example.com", // 邮箱域名false, // 是否有个人资料&createdAfter, // 注册时间)if err != nil {log.Printf("多条件查询失败: %v", err)} else {fmt.Printf("多条件查询成功: 找到%d个用户\n", len(users))}// 文章统计查询postStats, err := advancedQuery.GetPostsWithStatistics()if err != nil {log.Printf("文章统计查询失败: %v", err)} else {fmt.Printf("文章统计查询成功: 找到%d篇文章\n", len(postStats))for _, stat := range postStats {fmt.Printf(" 文章: %s, 评论数: %v, 标签数: %v\n", stat["title"], stat["comment_count"], stat["tag_count"])}}// 用户排行榜查询topUsers, err := advancedQuery.GetTopUsers("posts", 5)if err != nil {log.Printf("排行榜查询失败: %v", err)} else {fmt.Printf("排行榜查询成功: 找到%d个用户\n", len(topUsers))for i, user := range topUsers {fmt.Printf(" 第%d名: %s, 文章数: %v\n", i+1, user["username"], user["post_count"])}}// 全文搜索功能searchResults, err := advancedQuery.SearchWithFullText("Go", 10)if err != nil {log.Printf("全文搜索失败: %v", err)} else {fmt.Printf("全文搜索成功: 找到%d个结果\n", len(searchResults))for _, result := range searchResults {fmt.Printf(" 类型: %s, 标题: %s\n", result["type"], result["title"])}}
}// demonstrateOptimization 演示性能优化技巧
func demonstrateOptimization(db *gorm.DB) {fmt.Println("\n=== 性能优化演示 ===")optimizedQuery := optimization.NewOptimizedQuery(db)// 优化预加载查询start := time.Now()users, err := optimizedQuery.GetUsersWithOptimizedPreload()duration := time.Since(start)if err != nil {log.Printf("优化预加载查询失败: %v", err)} else {fmt.Printf("优化预加载查询成功: 找到%d个用户, 耗时: %v\n", len(users), duration)}// 创建优化索引if err := optimizedQuery.CreateOptimalIndexes(); err != nil {log.Printf("创建优化索引失败: %v", err)} else {fmt.Println("创建优化索引成功")}// 分析查询性能fmt.Println("\n查询性能分析:")if err := optimizedQuery.AnalyzeQueryPerformance("SELECT * FROM users WHERE is_active = ? ORDER BY created_at DESC LIMIT 10",true,); err != nil {log.Printf("查询性能分析失败: %v", err)}
}
8. 总结
8.1 GORM 核心概念
- 模型定义:用结构体定义数据模型,通过标签配置字段属性
- 关联关系:支持一对一、一对多、多对多等关联关系
- 自动迁移:自动创建和更新数据库表结构
- 查询构建器:链式调用构建复杂查询
- 预加载:解决 N+1 查询问题,提高查询效率
- 事务支持:确保数据一致性和完整性
8.2 最佳实践
8.2.1 模型设计
- 合理使用 GORM 标签配置字段属性
- 正确定义关联关系,避免循环引用
- 使用软删除保护重要数据
- 为经常查询的字段添加索引
8.2.2 查询优化
- 使用
Select只查询需要的字段 - 合理使用预加载,避免 N+1 查询
- 对预加载添加条件和限制
- 使用分页避免一次性加载大量数据
8.2.3 性能优化
- 创建合适的数据库索引
- 使用批量操作处理大量数据
- 合理配置数据库连接池
- 监控和分析慢查询
8.2.4 错误处理
- 检查所有数据库操作的错误
- 使用事务确保数据一致性
- 合理处理记录不存在的情况
- 记录和监控数据库错误
8.3 GORM vs 原生 SQL
| 特性 | GORM | 原生 SQL |
|---|---|---|
| 开发效率 | 高 | 中等 |
| 学习成本 | 中等 | 低 |
| 性能 | 良好 | 优秀 |
| 灵活性 | 中等 | 高 |
| 类型安全 | 高 | 低 |
| 代码维护 | 容易 | 中等 |
8.4 选择建议
适合使用 GORM 的场景:
- 快速开发原型和中小型项目
- 团队对 SQL 不够熟悉
- 需要跨数据库兼容性
- 重视开发效率和代码维护性
适合使用原生 SQL 的场景:
- 对性能要求极高的项目
- 需要复杂的 SQL 查询和优化
- 团队 SQL 技能较强
- 需要精确控制 SQL 执行
8.5 进阶学习
-
深入学习 GORM 高级特性
- 自定义数据类型
- 钩子函数(Hooks)
- 插件系统
- 数据库分片
-
数据库优化
- 索引设计和优化
- 查询性能分析
- 数据库监控
- 读写分离
-
架构设计
- 仓储模式(Repository Pattern)
- 领域驱动设计(DDD)
- 微服务数据管理
- 缓存策略
通过本章的学习,你应该已经掌握了 GORM 的基本使用方法和高级特性。GORM 作为 Go 语言最流行的 ORM 框架,能够大幅提高开发效率,但也要注意在性能敏感的场景下合理选择使用方式。在实际项目中,建议根据具体需求在 ORM 和原生 SQL 之间找到平衡点。
