GORM ORM 详解

GORM 通过将 Go 结构体(Go structs)映射到数据库表来简化数据库交互。了解如何在 GORM 中定义模型,是充分利用 GORM 全部功能的基础。

模型是使用普通结构体定义的。这些结构体可以包含具有基本 Go 类型、指针或这些类型的别名,甚至是自定义类型(只需要实现 database/sql 包中的 Scanner 和 Valuer 接口)。

1. 模型定义

gotype User struct {
  ID           uint           // Standard field for the primary key
  Name         string         // A regular string field
  Email        *string        // A pointer to a string, allowing for null values
  Age          uint8          // An unsigned 8-bit integer
  Birthday     *time.Time     // A pointer to time.Time, can be null
  MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings
  ActivatedAt  sql.NullTime   // Uses sql.NullTime for nullable time fields
  CreatedAt    time.Time      // Automatically managed by GORM for creation time
  UpdatedAt    time.Time      // Automatically managed by GORM for update time
  ignored      string         // 小写字母开头不会被映射
}

Go 的结构体 User → 数据库表 usersGormUserNamegorm_user_names。这是 GORM 的约定,方便你不需要手动指定表名。

GORM 提供了一个预定义的结构体,名为 gorm.Model,其中包含常用字段:

go// gorm.Model 的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

2. 字段权限控制

gotype User struct {
  Name string `gorm:"<-:create"` // 允许读和创建
  Name string `gorm:"<-:update"` // 允许读和更新
  Name string `gorm:"<-"`        // 允许读和写(创建和更新)
  Name string `gorm:"<-:false"`  // 允许读,禁止写
  Name string `gorm:"->"`        // 只读(除非有自定义配置,否则禁止写)
  Name string `gorm:"->;<-:create"` // 允许读和写
  Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
  Name string `gorm:"-"`  // 通过 struct 读写会忽略该字段
  Name string `gorm:"-:all"`        // 通过 struct 读写、迁移会忽略该字段
  Name string `gorm:"-:migration"`  // 通过 struct 迁移会忽略该字段
}

3. 链接到数据库

gopackage main

import (
	"fmt"
	"log"
	"time"

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

var DB *gorm.DB

func main() {
	dsn := "root:@tcp(117.50.217.36:3306)/mysql?charset=utf8mb4&parseTime=True&loc=Local"

	// 打印尝试连接的数据库信息
	fmt.Printf("尝试连接: %s@%s:%d/%s\n", "root", "117.50.217.36", 3306, "mysql")

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		// 打印详细错误信息
		log.Fatalf("连接数据库失败,详细错误: %v\n", err)
	}

	sqlDB, err := db.DB()
	if err != nil {
		log.Fatalf("获取底层连接池失败: %v\n", err)
	}

	sqlDB.SetMaxIdleConns(10)
	sqlDB.SetMaxOpenConns(100)
	sqlDB.SetConnMaxLifetime(time.Hour)

	fmt.Println("数据库连接成功!")
	DB = db
}

4. 自动迁移 AutoMigrate

自动迁移(AutoMigrate)是 GORM 提供的一个功能,它可以自动将你定义的 Go 结构体转换为数据库中的表结构,并保持同步。简单说:你只管写 Go 代码定义模型,GORM 自动帮你创建/更新数据库表。不会删除已有列。

功能说明示例
创建表结构体 → 数据库表User 结构体 → users 表
添加字段结构体新增字段 → 表新增列加 Email 字段 → 表加 email 列
修改字段类型字段类型改变 → 列类型改变int 改 string → 列类型变更
设置约束标签定义的约束 → 数据库约束not null, unique, default 等
创建索引标签定义索引 → 数据库索引gorm:"index", gorm:"uniqueIndex"
修改字段属性字段属性改变 → 列属性改变size:100 → VARCHAR(100)
gopackage main

import (
	"fmt"
	"time"

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

type User struct {
	ID        uint
	Name      string
	Age       int
	Email     string
	CreatedAt time.Time
	UpdatedAt time.Time
}

func main() {
	dsn := "root:@tcp(117.50.217.36:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("连接失败: " + err.Error())
	}

	// 自动创建表(如果表不存在就创建,存在就检查字段)
	db.AutoMigrate(&User{})

	fmt.Println("表创建/同步成功!")
}

5. CRUD 操作

gopackage main

import (
	"fmt"
	"time"

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

type User struct {
	ID        uint
	Name      string
	Age       int
	Email     string
	CreatedAt time.Time
	UpdatedAt time.Time
}

func main() {
	dsn := "root:@tcp(117.50.217.36:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("连接失败: " + err.Error())
	}

	// 自动创建表
	db.AutoMigrate(&User{})
	fmt.Println("表创建/同步成功!")

	// ========== 1. 创建记录 (Create) ==========
	createUser(db)

	// ========== 2. 批量创建 ==========
	batchCreateUsers(db)

	// ========== 3. 查询记录 (Read) ==========
	queryUser(db)

	// ========== 4. 更新记录 (Update) ==========
	updateUser(db)

	// ========== 5. 删除记录 (Delete) ==========
	deleteUser(db)

	// ========== 6. 复杂查询示例 ==========
	complexQuery(db)
}

5.1 创建单条记录

gofunc createUser(db *gorm.DB) {
	fmt.Println("\n--- 创建用户 ---")
	
	user := User{
		Name:  "张三",
		Age:   25,
		Email: "zhangsan@example.com",
	}
	
	result := db.Create(&user)
	if result.Error != nil {
		fmt.Printf("创建失败: %v\n", result.Error)
		return
	}
	
	fmt.Printf("创建成功!用户ID: %d, 姓名: %s, 年龄: %d, 邮箱: %s\n", 
		user.ID, user.Name, user.Age, user.Email)
}

5.2 批量创建

gofunc batchCreateUsers(db *gorm.DB) {
	fmt.Println("\n--- 批量创建用户 ---")
	
	users := []User{
		{Name: "李四", Age: 30, Email: "lisi@example.com"},
		{Name: "王五", Age: 28, Email: "wangwu@example.com"},
		{Name: "赵六", Age: 35, Email: "zhaoliu@example.com"},
	}
	
	result := db.Create(&users)
	if result.Error != nil {
		fmt.Printf("批量创建失败: %v\n", result.Error)
		return
	}
	
	fmt.Printf("批量创建成功!共创建 %d 条记录\n", result.RowsAffected)
}

5.3 查询记录

gofunc queryUser(db *gorm.DB) {
	fmt.Println("\n--- 查询用户 ---")
	
	var user User
	var users []User
	
	// 查询单条记录(根据主键)
	db.First(&user, 1)
	fmt.Printf("查询ID=1的用户: 姓名=%s, 年龄=%d, 邮箱=%s\n", user.Name, user.Age, user.Email)
	
	// 查询单条记录(根据条件)
	db.Where("name = ?", "张三").First(&user)
	fmt.Printf("查询姓名为张三的用户: ID=%d, 年龄=%d, 邮箱=%s\n", user.ID, user.Age, user.Email)
	
	// 查询所有记录
	db.Find(&users)
	fmt.Printf("查询所有用户: 共 %d 条记录\n", len(users))
	for _, u := range users {
		fmt.Printf("  - ID:%d, 姓名:%s, 年龄:%d, 邮箱:%s\n", u.ID, u.Name, u.Age, u.Email)
	}
	
	// 条件查询
	var youngUsers []User
	db.Where("age < ?", 30).Find(&youngUsers)
	fmt.Printf("\n年龄小于30的用户: 共 %d 条\n", len(youngUsers))
	
	// 查询指定字段
	var names []string
	db.Model(&User{}).Select("name").Find(&names)
	fmt.Printf("所有用户姓名: %v\n", names)
}

5.4 更新记录

gofunc updateUser(db *gorm.DB) {
	fmt.Println("\n--- 更新用户 ---")
	
	// 先查询要更新的用户
	var user User
	db.First(&user, 1)
	
	// 更新单个字段
	db.Model(&user).Update("age", 26)
	fmt.Printf("更新用户ID=%d的年龄为26\n", user.ID)
	
	// 更新多个字段
	db.Model(&user).Updates(User{
		Name:  "张三-更新",
		Email: "zhangsan_new@example.com",
	})
	fmt.Printf("更新用户ID=%d的姓名和邮箱\n", user.ID)
	
	// 使用 map 更新(不会忽略零值)
	db.Model(&user).Updates(map[string]interface{}{
		"name": "张三-map更新",
		"age":  27,
	})
	fmt.Printf("使用map更新用户ID=%d\n", user.ID)
	
	// 批量更新(更新所有符合条件的记录)
	db.Model(&User{}).Where("age > ?", 30).Update("age", 31)
	fmt.Println("批量更新:将所有年龄大于30的用户年龄改为31")
}

5.5 删除记录

gofunc deleteUser(db *gorm.DB) {
	fmt.Println("\n--- 删除用户 ---")
	
	// 删除单条记录(需要主键)
	var user User
	db.First(&user, "name = ?", "赵六")
	if user.ID != 0 {
		db.Delete(&user)
		fmt.Printf("删除用户: %s (ID=%d)\n", user.Name, user.ID)
	}
	
	// 批量删除
	result := db.Where("age > ?", 30).Delete(&User{})
	fmt.Printf("批量删除年龄大于30的用户: 删除了 %d 条记录\n", result.RowsAffected)
	
	// 注意:由于 User 结构体没有 gorm.DeletedAt 字段
	// 这里的删除是物理删除(永久删除数据)
}

5.6 复杂查询示例

gofunc complexQuery(db *gorm.DB) {
	fmt.Println("\n--- 复杂查询 ---")
	
	var users []User
	
	// IN 查询
	db.Where("age IN ?", []int{25, 28, 30}).Find(&users)
	fmt.Printf("年龄在25,28,30的用户: %d 条\n", len(users))
	
	// LIKE 查询
	db.Where("name LIKE ?", "%张%").Find(&users)
	fmt.Printf("姓名包含'张'的用户: %d 条\n", len(users))
	
	// AND 条件
	db.Where("name = ? AND age > ?", "张三", 20).Find(&users)
	fmt.Printf("姓名为张三且年龄大于20的用户: %d 条\n", len(users))
	
	// OR 条件
	db.Where("name = ?", "张三").Or("name = ?", "李四").Find(&users)
	fmt.Printf("姓名为张三或李四的用户: %d 条\n", len(users))
	
	// 排序
	db.Order("age desc").Find(&users)
	fmt.Println("按年龄降序排列:")
	for _, u := range users {
		fmt.Printf("  - %s: %d岁\n", u.Name, u.Age)
	}
	
	// 分页查询(Limit + Offset)
	var pagedUsers []User
	db.Limit(2).Offset(0).Find(&pagedUsers)
	fmt.Printf("分页查询第1页(每页2条): %d 条\n", len(pagedUsers))
	
	// 统计数量
	var count int64
	db.Model(&User{}).Where("age > ?", 25).Count(&count)
	fmt.Printf("年龄大于25岁的用户数量: %d\n", count)
}

6. 原生 SQL

当 GORM 的链式调用无法满足需求时,可以直接使用原生 SQL:

CRUD 操作SQL 生成器方式原生 SQL 方式
创建db.Create(&user)db.Exec("INSERT INTO users ...")
查询单条db.First(&user, 1)db.Raw("SELECT * FROM users WHERE id = ?", 1).Scan(&user)
查询多条db.Where("age > ?", 18).Find(&users)db.Raw("SELECT * FROM users WHERE age > ?", 18).Scan(&users)
更新db.Model(&user).Update("age", 26)db.Exec("UPDATE users SET age = ? WHERE id = ?", 26, user.ID)
删除db.Delete(&user)db.Exec("DELETE FROM users WHERE id = ?", user.ID)
目录