54·数据库进阶

GORM ORM

gormormmodelmigration

第54课:GORM ORM - 高效的数据库交互

1. 学习目标

完成本课学习后,你将能够:

  • 理解 ORM 的概念及其在 Go 语言中的优势。
  • 使用 GORM 连接 MySQL 数据库并定义数据模型。
  • 掌握 GORM 的基本 CRUD(创建、读取、更新、删除)操作。
  • 使用 GORM 的自动迁移功能来同步数据表结构。
  • 处理 GORM 查询中的关联关系与基本条件筛选。

2. 核心概念

什么是 ORM?

ORM(对象关系映射) 是一种编程技术,它将程序中的对象(Object)与数据库中的表(Relation)进行映射。简单来说,它让你可以用操作 Go 语言结构体(struct)和对象的方式,来直接操作数据库,而无需手动编写复杂的 SQL 语句。

优势:

  • 开发效率高:用熟悉的 Go 语法操作数据库,减少 SQL 编写量。
  • 可移植性强:更换数据库时,只需修改少量配置,大部分代码无需改动。
  • 安全性:GORM 默认使用参数化查询,能有效防止 SQL 注入攻击。

GORM 是什么?

GORM 是 Go 语言中非常流行的一个 ORM 库。它功能强大、设计优雅,并且拥有丰富的文档和社区支持。

GORM 的核心组件:

  1. 模型(Model):一个普通的 Go 结构体,用于映射数据库表。
  2. 会话(Session):通过 db *gorm.DB 这个对象与数据库进行交互。
  3. 迁移(Migration):根据模型自动创建或更新数据库表结构。

3. 代码示例

以下是一个完整的示例,展示如何使用 GORM 连接 MySQL、定义模型、执行基本操作并进行自动迁移。

package main

import (
	"fmt"
	"log"
	"time"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// 1. 定义模型(Model)
// 模型通常继承 gorm.Model,它提供了 ID, CreatedAt, UpdatedAt, DeletedAt 字段。
type Product struct {
	gorm.Model
	Code   string `gorm:"type:varchar(100);uniqueIndex"`
	Price  uint
	Name   string `gorm:"size:255"` // 定义字段大小
	Status bool   `gorm:"default:true"`
}

// 你可以定义自定义的表名
func (Product) TableName() string {
	return "products"
}

func main() {
	// 2. 连接数据库
	// 请根据你的实际情况修改 DSN(数据源名称)
	dsn := "root:yourpassword@tcp(127.0.0.1:3306)/yourdb?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info), // 开启SQL日志
	})
	if err != nil {
		log.Fatal("连接数据库失败:", err)
	}
	fmt.Println("✅ 数据库连接成功")

	// 3. 自动迁移
	// 根据模型结构自动创建或更新表
	err = db.AutoMigrate(&Product{})
	if err != nil {
		log.Fatal("自动迁移失败:", err)
	}
	fmt.Println("✅ 自动迁移完成")

	// 4. 创建记录 (Create)
	product := Product{
		Code:  "D42",
		Price: 100,
		Name:  "测试商品",
	}
	result := db.Create(&product)
	if result.Error != nil {
		log.Fatal("创建记录失败:", result.Error)
	}
	fmt.Printf("✅ 创建记录成功,影响行数: %d,自增ID: %d\n", result.RowsAffected, product.ID)

	// 5. 查询记录 (Read)
	// 查询单条记录
	var queriedProduct Product
	db.First(&queriedProduct, 1) // 根据主键查找
	fmt.Printf("🔍 查询ID为1的商品: %+v\n", queriedProduct)

	// 根据条件查询
	var products []Product
	db.Where("price > ?", 50).Find(&products)
	fmt.Printf("🔍 价格大于50的商品数量: %d\n", len(products))

	// 6. 更新记录 (Update)
	// 更新单个字段
	db.Model(&queriedProduct).Update("Price", 200)
	// 更新多个字段
	db.Model(&queriedProduct).Updates(Product{Price: 300, Status: false})
	// 使用 map 更新(注意:map 方式不会更新零值字段)
	// db.Model(&queriedProduct).Updates(map[string]interface{}{"Price": 300, "Status": false})

	// 7. 删除记录 (Delete)
	// 注意:GORM 默认是软删除(更新 DeletedAt 字段)
	db.Delete(&queriedProduct)
	fmt.Println("🗑️  记录已删除(软删除)")

	// 查询会自动排除软删除的记录
	var count int64
	db.Model(&Product{}).Count(&count)
	fmt.Printf("📦 当前商品总数(不含已删除): %d\n", count)

	// 8. 事务处理示例(简单)
	tx := db.Begin()
	// 在事务中执行一些操作
	phone := Product{Code: "Phone001", Price: 999, Name: "智能手机"}
	if err := tx.Create(&phone).Error; err != nil {
		tx.Rollback() // 出错回滚
		return
	}
	tx.Commit() // 提交事务
	fmt.Println("✅ 事务提交成功")
}

4. 实践练习

练习1:基础模型与CRUD

要求:

  1. 定义一个 User 结构体,包含 IDUsernameEmailAge 字段。
  2. 使用 GORM 自动迁移创建 users 表。
  3. 编写代码,创建 3 个用户记录。
  4. 查询年龄大于 20 岁的用户,并打印其用户名。

练习2:关联查询(选做)

要求:

  1. 在上面的 Product 模型基础上,定义一个 Category 模型(包含 IDName)。
  2. Product 中添加一个 CategoryID 字段,表示关联关系。
  3. 创建一个商品分类,并创建属于该分类的商品。
  4. 查询某个分类下的所有商品(使用 Preload 预加载关联)。

练习3:分页与排序

要求:

  1. products 表中插入至少 10 条记录。
  2. 查询第 2 页的数据(每页 5 条),并按 price 降序排序。
  3. 使用 db.Scopes() 封装一个分页函数。

5. 常见错误

  1. 结构体字段未导出:GORM 要求用于映射的结构体字段必须是大写开头(导出)。小写字段会被忽略。

    // 错误
    type User struct {
        name string // 小写开头,GORM 无法访问
    }
    // 正确
    type User struct {
        Name string // 大写开头
    }
    
  2. 自动迁移时主键冲突:如果一个结构体有多个字段,且其中一个不是 ID 但被误当作主键,可能会导致问题。确保继承 gorm.Model 或显式定义 primaryKey 标签。

  3. 更新零值字段:使用 Updates 方法传入结构体时,GORM 不会更新零值字段(如 0false"")。如需更新零值字段,请使用 map[string]interface{}Select 明确指定字段。

    db.Model(&user).Select("Age").Update("Age", 0) // 强制将 Age 更新为 0
    
  4. 忘记处理错误:GORM 的大多数操作都返回 *gorm.DB 对象,其中包含 Error 字段。务必检查 result.Error,否则数据库错误会被静默忽略。

    // 错误
    db.Create(&product)
    // 正确
    result := db.Create(&product)
    if result.Error != nil {
        // 处理错误
    }
    
  5. 软删除的误解db.Delete(&model) 默认执行的是软删除(更新 deleted_at 字段)。查询时,GORM 会自动添加 WHERE deleted_at IS NULL 条件。如需物理删除,可使用 db.Unscoped().Delete(&model)

6. 小结

  • ORM 是将对象与数据库表进行映射的技术,GORM 是 Go 语言中功能强大且流行的 ORM 库。
  • 使用 gorm.Open 连接数据库,并通过模型结构体定义数据表。
  • 自动迁移 (AutoMigrate) 是同步模型与数据库表结构的利器,适合在开发阶段使用。
  • 核心的 CRUD 操作通过 CreateFirst/FindUpdateDelete 方法实现。
  • 务必检查操作返回的错误,并理解软删除与物理删除的区别。
  • 通过练习掌握基础后,可以进一步学习 GORM 的关联、预加载、钩子、复杂查询等高级特性。

练习编辑器

go
Loading...

继续学习

完成本课后,建议继续学习下一课「Redis 操作」 以巩固所学知识。