Go 开发者必看:Gorm 核心用法详解(含完整代码示例)

21 阅读16分钟

Gorm 是 Go 语言中最流行的 ORM(对象关系映射)框架,以简洁的 API、强大的功能和良好的扩展性被广泛应用。本文将以学生表、课程表、分数表的关联关系为核心场景,手把手演示 Gorm 的核心功能,帮助你快速掌握 Gorm 的使用精髓。

环境准备

首先确保你的开发环境满足以下条件:

  • Go 1.18+(推荐 1.20+)
  • 安装 Gorm 和 MySQL 驱动:
go get gorm.io/gorm
go get gorm.io/driver/mysql

连接数据库

Gorm 连接数据库的核心是 Open 方法,结合 MySQL 驱动的 DSN(数据源名称)实现连接。同时我们会配置日志打印(便于调试 SQL)、连接池、超时等常用参数。核心配置参数说明 :

参数作用
SlowThreshold慢 SQL 阈值,超过该时间的 SQL 会被标记为慢查询并打印
LogLevel日志级别(Silent/Error/Warn/Info),Info 级别会打印所有执行的 SQL
SetMaxOpenConns数据库最大打开连接数,避免连接数过多导致数据库压力过大
SetMaxIdleConns最大空闲连接数,保留一定空闲连接提高复用率
PrepareStmt预编译 SQL 并缓存,重复执行相同 SQL 时提升性能
package main
import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
    "log"
    "os"
    "time"
)

// 全局DB对象
var db *gorm.DB

// 初始化数据库连接
func initDB() error {
    // DSN格式:user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local
    dsn := "root:123456@tcp(127.0.0.1:3306)/gorm_demo?charset=utf8mb4&parseTime=True&loc=Local"

    // 配置日志(打印SQL)
    newLogger := logger.New(
        log.New(os.Stdout, "\r\n", log.LstdFlags), // 输出到控制台
        logger.Config{
            SlowThreshold: time.Second,   // 慢SQL阈值(超过1秒打印)
            LogLevel:      logger.Info,   // 日志级别:Info(打印所有SQL)、Warn(警告+错误)、Error(仅错误)
            Colorful:      true,          // 彩色打印
        },
    )

    // 连接数据库并配置参数
    var err error
    db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: newLogger, // 日志配置
        // 其他常用配置
        SkipDefaultTransaction: true, // 关闭默认事务(提高性能)
        PrepareStmt:            true, // 预编译语句(缓存SQL,提高执行效率)
        // 全局表名规则:给所有表加前缀 t_(优先级低于结构体TableName方法)
        NamingStrategy: schema.NamingStrategy{
            TablePrefix:   "t_", // 表名前缀
            SingularTable: false, // 表名是否单数(默认复数,如student→students,开启后为student)
        },
    })
    if err != nil {
        return err
    }

    // 获取底层sql.DB对象,配置连接池
    sqlDB, err := db.DB()
    if err != nil {
        return err
    }
    // 连接池配置
    sqlDB.SetMaxOpenConns(100)    // 最大打开连接数
    sqlDB.SetMaxIdleConns(20)     // 最大空闲连接数
    sqlDB.SetConnMaxLifetime(1h)  // 连接最大存活时间
    sqlDB.SetConnMaxIdleTime(30m) // 连接最大空闲时间

    return nil
}

func main() {
    // 初始化数据库
    if err := initDB(); err != nil {
        log.Fatalf("数据库连接失败:%v", err)
    }
    log.Println("数据库连接成功")
}

定义 Model 结构体

在连接数据库成功后,AutoMigrate 会根据结构体自动创建 / 更新表结构(仅新增字段,不会删除已有字段),创建表的字段类型,在gOrm官网的声明模型里设置,创建数据表。

// BaseModel 基础模型(所有表共用字段)
type BaseModel struct {
    ID        uint           `gorm:"primaryKey;autoIncrement" json:"id"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"` // 逻辑删除字段
}

AutoMigrate 自动建表

AutoMigrate 会根据结构体自动创建 / 更新表结构(仅新增字段,不会删除已有字段),创建表的字段类型,在gOrm官网的声明模型里设置,结合逻辑删除一起演示:

在这里有一个特别的说明,在Go语言中的整数类型对应Mysql中的整数如下:

  • Go 的 int 类型(在 64 位系统上)会被 GORM 映射为 MySQL 的 BIGINT
  • Go 的 int32 类型会被映射为 MySQL 的 INT
  • Go 的 int16 类型会被映射为 MySQL 的 SMALLINT
  • Go 的 int8 类型会被映射为 MySQL 的 TINYINT
// Student 学生表
type Student struct {
    BaseModel
    Name      string  `gorm:"size:20;not null;default:'';column:student_name" json:"name"` // 姓名
    Age       int32   `gorm:"default:0;not null" json:"age"`                               // 年龄
    Class     string  `gorm:"size:30" json:"class"`                                        // 班级
    StudentNo string  `gorm:"size:20;uniqueIndex" json:"student_no"`                       // 学号
    Scores    []Score `gorm:"foreignKey:StudentID" json:"scores"`                          // 关联分数(一对多)
    //Courses []Course `gorm:"many2many:scores;foreignKey:StudentID;joinForeignKey:CourseID" json:"courses"`//关联课程(多对多)
}

// Course 课程表
type Course struct {
    BaseModel
    Name     string `gorm:"size:50;not null" json:"name"` // 课程名
    Teacher  string `gorm:"size:20" json:"teacher"`      // 授课老师
    CourseNo string `gorm:"size:20;uniqueIndex" json:"course_no"` // 课程号
    Scores   []Score `gorm:"foreignKey:CourseID" json:"scores"` // 关联分数(一对多)
}

// Score 分数表(关联学生和课程)
type Score struct {
    BaseModel
    StudentID uint    `gorm:"index" json:"student_id"` // 学生ID(外键)
    CourseID  uint    `gorm:"index" json:"course_id"`  // 课程ID(外键)
    Score     float64 `gorm:"type:decimal(5,2);default:0" json:"score"` // 分数
}

// 自动建表
func migrateTables() {
    // AutoMigrate 会自动创建/更新表结构
    err := db.AutoMigrate(&Student{}, &Course{}, &Score{})
    if err != nil {
        log.Fatalf("建表失败:%v", err)
    }
    log.Println("表结构同步成功")
}

// 在main函数中调用
func main() {
    if err := initDB(); err != nil {
        log.Fatalf("数据库连接失败:%v", err)
    }
    migrateTables() // 执行建表
}

Gorm 表结构定义依赖结构体标签(tag) ,核心标签说明:

标签作用示例
primaryKey标记主键gorm:"primaryKey"
autoIncrement自增gorm:"autoIncrement"
size字段长度gorm:"size:20"
not null非空约束gorm:"not null"
default默认值gorm:"default:0"
index创建索引gorm:"index"
uniqueIndex创建唯一索引gorm:"uniqueIndex"
foreignKey外键关联gorm:"foreignKey:StudentID"
type指定数据库字段类型gorm:"type:decimal(5,2)"

关键注意点

  • 结构体字段名首字母必须大写(Go 可见性规则);
  • 外键默认规则:关联表的字段名 + ID(如 Student 关联 Score,外键为 StudentID);
  • 小数类型推荐用 decimal(5,2)(5 位总长度,2 位小数),避免 float 精度问题。

自定义表名

Gorm 默认将结构体名转为小写复数作为表名(如 Student → students),可通过 TableName 方法自定义:

// 给Student结构体添加TableName方法
func (s Student) TableName() string {
    return "t_student" // 自定义表名为t_student
}

// Course自定义表名
func (c Course) TableName() string {
    return "t_course"
}

// Score自定义表名
func (s Score) TableName() string {
    return "t_score"
}

增删改查

Gorm 的新增、与批量新增

单条插入(Create)操作如下:

func insertSingleData() {
    // 插入学生
    student := Student{
        Name:     "张三",
        Age:      18,
        Class:    "高一(1)班",
        StudentNo: "2024001",
    }
    result := db.Create(&student) // 插入后,student.ID 会被自动赋值
    if result.Error != nil {
        log.Printf("插入学生失败:%v", result.Error)
    } else {
        log.Printf("插入学生成功,ID:%d", student.ID)
    }
}

//执行结果
//2026/02/05 11:45:50 插入学生成功,ID:1
//2026/02/05 11:45:50 E:/GoProject/go-grpc/gorm/main.go:115
//[8.521ms] [rows:1] INSERT INTO `t_students` 
//(`created_at`,`updated_at`,`deleted_at`,`student_name`,`age`,`class`,`student_no`)
//VALUES ('2026-02-05 11:45:50.77','2026-02-05 11:45:50.77',NULL,'张三',18,'高一(1)班','2024001')

批量插入推荐使用 CreateInBatches,指定批次大小,避免单次插入数据过多:

func insertBatchData() {
    // 批量插入学生
    students := []Student{
        {Name: "李四", Age: 17, Class: "高一(1)班", StudentNo: "2024002"},
        {Name: "王五", Age: 18, Class: "高一(2)班", StudentNo: "2024003"},
        {Name: "赵六", Age: 17, Class: "高一(2)班", StudentNo: "2024004"},
    }
    // 批次大小为2,自动分批次插入
    result := db.CreateInBatches(&students, 2)
    if result.Error != nil {
        log.Printf("批量插入学生失败:%v", result.Error)
    } else {
        log.Printf("批量插入成功,行数:%d", result.RowsAffected)
    }
}

Gorm 的删除操作

Gorm 支持逻辑删除(默认)和物理删除两种方式,其中逻辑删除是企业开发的主流方式(保留数据溯源能力),物理删除需谨慎使用。

核心原理:基于结构体中 gorm.DeletedAt 字段,执行 Delete 操作时,Gorm 不会删除数据,而是将 deleted_at 字段更新为当前时间;查询时会自动添加 deleted_at IS NULL 条件,过滤已删除数据。

1、基础逻辑删除(单条 / 批量)

// 1. 根据主键删除单条数据(逻辑删除)
func softDeleteSingle() {
    // 删除ID=1的学生(实际更新deleted_at)
    result := db.Delete(&Student{}, 1)
    if result.Error != nil {
        log.Printf("逻辑删除失败:%v", result.Error)
    } else {
        log.Printf("逻辑删除成功,影响行数:%d", result.RowsAffected)
    }

    // 2. 条件逻辑删除(删除高一(1)班的所有学生)
    result = db.Where("class = ?", "高一(1)班").Delete(&Student{})
    log.Printf("批量逻辑删除影响行数:%d", result.RowsAffected)
}

//2026/02/05 12:11:57 E:/GoProject/go-grpc/gorm/main.go:170
//[6.836ms] [rows:1] UPDATE `t_students` SET `deleted_at`='2026-02-05 12:11:57'
// WHERE `t_students`.`id` = 1 AND `t_students`.`deleted_at` IS NULL

//2026/02/05 12:11:57 E:/GoProject/go-grpc/gorm/main.go:178
//[4.113ms] [rows:1] UPDATE `t_students` SET `deleted_at`='2026-02-05 12:11:57.007' 
// WHERE class = '高一(1)班' AND `t_students`.`deleted_at` IS NULL

2、查询已被逻辑删除的数据

默认查询会过滤已删除数据,如需查询,需使用 Unscoped() 取消过滤:

func queryDeletedData() {
    var student Student
    // 查询已逻辑删除的ID=1的学生
    db.Unscoped().First(&student, 1)
    log.Printf("已删除的学生信息:%+v", student)

    // 查询所有学生(包含已逻辑删除的)
    var students []Student
    db.Unscoped().Find(&students)
    log.Printf("所有学生(含已删除):%+v", students)
}

2026/02/05 12:14:21 E:/GoProject/go-grpc/gorm/main.go:186
[7.286ms] [rows:1] SELECT * FROM `t_students` WHERE `t_students`.`id` = 1 ORDER BY `t_students`.`id` LIMIT 1

2026/02/05 12:14:21 E:/GoProject/go-grpc/gorm/main.go:191
[1.862ms] [rows:4] SELECT * FROM `t_students`

3、物理删除(Hard Delete)

如需彻底删除逻辑删除的数据,需 Unscoped() + Delete,直接从数据库中删除数据,无恢复可能,仅推荐用于测试 / 临时数据场景。

逻辑删除关键注意事项:

  • 必须在结构体中嵌入 gorm.DeletedAt 字段,否则 Delete 操作会直接物理删除;
  • 逻辑删除后,Count()Find() 等查询方法会自动过滤已删除数据;
  • 如需对逻辑删除的数据操作(查询 / 恢复 / 永久删除),必须加 Unscoped()
func permanentDelete() {
    // 永久删除ID=1的学生(从数据库中彻底删除)
    result := db.Unscoped().Delete(&Student{}, 1)
    if result.Error != nil {
        log.Printf("永久删除失败:%v", result.Error)
    } else {
        log.Printf("永久删除成功,影响行数:%d", result.RowsAffected)
    }
}

// 物理删除(不推荐生产环境使用)
func hardDelete() {
    // 方式1:直接物理删除单条数据
    db.Unscoped().Delete(&Score{}, 1) // 删除ID=1的分数记录

    // 方式2:条件物理删除(删除分数<60的记录)
    db.Unscoped().Where("score < ?", 60).Delete(&Score{})
}

Gorm 的更新操作

Gorm 提供了丰富的更新方式,覆盖单字段、多字段、条件更新、批量更新等场景,核心方法包括 SaveUpdateUpdates,需重点注意零值更新字段选择问题。

更新操作核心

  • Save 全字段更新(含零值),Updates 推荐用 Map 避免零值忽略问题;
  • 条件更新需先 Where 再更新,批量更新注意条件范围,避免全表更新;
  • 更新钩子(BeforeUpdate/AfterUpdate)可实现数据校验、日志记录等自定义逻辑;
  • Select/Omit 可精准控制要更新 / 忽略的字段,提升更新效率。

1、Save:全字段更新(含零值)

Save 会根据主键判断:主键存在则更新所有字段(包括零值),主键不存在则新增;适合需要全量更新的场景。

func updateWithSave() {
    // 1. 先查询要更新的学生
    var student Student
    db.First(&student, 2) // 查询ID=2的学生

    // 2. 修改字段(包括零值)
    student.Age = 0       // 零值
    student.Class = "高二(1)班"

    // 3. 全字段更新
    result := db.Save(&student)
    if result.Error != nil {
        log.Printf("Save更新失败:%v", result.Error)
    } else {
        log.Printf("Save更新成功,影响行数:%d", result.RowsAffected)
    }
}

2、Update:单字段更新

仅更新指定的单个字段,支持条件更新。

func updateSingleField() {
    // 1. 根据主键更新单字段(更新ID=2的学生姓名)
    result := db.Model(&Student{}).Where("id = ?", 2).Update("name", "张三-更新后")
    log.Printf("单字段更新影响行数:%d", result.RowsAffected)

    // 2. 条件更新单字段(将高一(2)班学生的年龄+1)
    db.Model(&Student{}).Where("class = ?", "高一(2)班").Update("age", gorm.Expr("age + ?", 1))
}

3、Updates:多字段更新(推荐)

支持通过结构体Map更新多字段,是最常用的更新方式;需注意:结构体更新会忽略零值,Map 更新支持零值。

func updateMultiFields() {
    var student Student
    db.First(&student, 3) // 查询ID=3的学生

    // 方式1:结构体更新(忽略零值)
    // 注意:Age=0 会被忽略,仅更新Name和Class
    student.Name = "王五-更新后"
    student.Age = 0
    student.Class = "高二(2)班"
    db.Model(&student).Updates(student)

    // 方式2:Map更新(支持零值,推荐)
    // Age=0 会被正常更新,无忽略问题
    db.Model(&student).Updates(map[string]interface{}{
        "name":  "王五-最终版",
        "age":   0,
        "class": "高二(2)班",
    })

    // 方式3:指定字段更新(强制更新零值)
    // Select指定要更新的字段,即使是零值也会更新
    db.Model(&student).Select("age", "name").Updates(Student{Name: "王五-指定字段", Age: 0})

    // 方式4:忽略指定字段更新
    // Omit排除不需要更新的字段,其余字段正常更新
    db.Model(&student).Omit("class").Updates(map[string]interface{}{
        "name": "王五-忽略字段",
        "age":  19,
        "class": "高三(1)班", // 被Omit排除,不会更新
    })
}

4、条件更新 & 批量更新

func batchUpdate() {
    // 1. 条件批量更新(更新所有18岁学生的班级为"高三(1)班")
    result := db.Model(&Student{}).Where("age = ?", 18).Updates(map[string]interface{}{
        "class": "高三(1)班",
    })
    log.Printf("批量更新影响行数:%d", result.RowsAffected)

    // 2. 无条件批量更新(慎用!更新全表)
    // db.Model(&Student{}).Updates(map[string]interface{}{"class": "默认班级"})
}

5、关联更新(更新关联表数据)

func updateAssociation() {
    // 更新学生ID=1的所有分数(分数+5分)
    db.Model(&Student{}).Where("id = ?", 1).Association("Scores").Update("score", gorm.Expr("score + ?", 5))

    // 另一种方式:直接更新分数表(关联学生ID)
    db.Model(&Score{}).Where("student_id = ?", 1).Update("score", gorm.Expr("score + ?", 5))
}

6、更新钩子(BeforeUpdate/AfterUpdate)

Gorm 支持更新生命周期钩子,可在更新前 / 后执行自定义逻辑(如更新时间、数据校验)。

// 给Student结构体添加更新钩子
func (s *Student) BeforeUpdate(tx *gorm.DB) error {
    // 更新前校验:年龄不能小于0
    if s.Age < 0 {
        return fmt.Errorf("年龄不能为负数")
    }
    // 自定义逻辑:更新时记录操作日志
    log.Printf("准备更新学生ID=%d的信息", s.ID)
    return nil
}

func (s *Student) AfterUpdate(tx *gorm.DB) error {
    // 更新后执行:记录更新日志
    log.Printf("学生ID=%d的信息已更新", s.ID)
    return nil
}

// 测试更新钩子
func testUpdateHook() {
    var student Student
    db.First(&student, 4)
    student.Age = 20
    // 更新时会自动触发BeforeUpdate → 执行更新 → AfterUpdate
    db.Save(&student)
}

Gorm 的查询操作

Gorm 提供 First/Last/Take 三种获取单条数据的方法,核心区别在于排序规则:

func testGetSingleData() {
    var student Student

    // First:按主键升序取第一条(默认)
    db.First(&student)
    log.Printf("First获取:%+v", student)

    // Last:按主键降序取最后一条
    db.Last(&student)
    log.Printf("Last获取:%+v", student)

    // Take:不指定排序,取第一条(性能略高)
    db.Take(&student)
    log.Printf("Take获取:%+v", student)

    // 带条件的查询
    db.Where("name = ?", "张三").First(&student)
    log.Printf("条件查询:%+v", student)
}

//2026/02/05 12:29:23 E:/GoProject/go-grpc/gorm/main.go:204
//[2.543ms] [rows:1] SELECT * FROM `t_students` WHERE `t_students`.`deleted_at` IS NULL AND `t_students`.`id` = 3 ORDER BY `t_students`.`id` DESC LIMIT 1

//2026/02/05 12:29:23 E:/GoProject/go-grpc/gorm/main.go:208
//[1.638ms] [rows:1] SELECT * FROM `t_students` WHERE `t_students`.`deleted_at` IS NULL AND `t_students`.`id` = 3 LIMIT 1

//2026/02/05 12:29:24 E:/GoProject/go-grpc/gorm/main.go:212 Error 1054 (42S22): Unknown column 'name' in 'where clause'
//[2.772ms] [rows:0] SELECT * FROM `t_students` WHERE name = '张三' AND `t_students`.`deleted_at` IS NULL AND `t_students`.`id` = 3 ORDER BY `t_students`.`id` LIMIT 1

注意:如果查询无结果,Gorm 会返回 gorm.ErrRecordNotFound 错误,可通过 errors.Is 判断:

import "errors"

if errors.Is(result.Error, gorm.ErrRecordNotFound) {
    log.Println("未找到数据")
}

Gorm 支持丰富的查询语法,以下是最常用的查询场景:

func testBasicQuery() {
    var students []Student
    var count int64

    // 1. 条件查询(等于、IN、模糊查询)
    // 等于
    db.Where("age = ?", 18).Find(&students)
    // IN
    db.Where("age IN ?", []int{17, 18}).Find(&students)
    // 模糊查询
    db.Where("name LIKE ?", "%张%").Find(&students)

    // 2. 计数
    db.Model(&Student{}).Where("class = ?", "高一(1)班").Count(&count)
    log.Printf("高一(1)班学生数:%d", count)

    // 3. 分页查询(Offset:偏移量,Limit:每页条数)
    page := 1
    pageSize := 10
    db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&students)

    // 4. 排序
    db.Order("age DESC, created_at ASC").Find(&students)

    // 5. 选择指定字段
    db.Select("id, name, age").Find(&students)

    // 6. 链式查询(Gorm支持链式调用)
    db.Where("class = ?", "高一(2)班").Order("age ASC").Limit(5).Find(&students)
}

关联操作(HasMany / 多对多 / Preload/Joins)

1、HasMany 关系(学生 - 分数 一对多)

Student 结构体中已通过 Scores []Score 定义了 HasMany 关系,可直接关联查询:

// 查询学生及其所有分数
func testHasMany() {
    var student Student
    // Preload:预加载关联数据(N+1查询问题,先查学生,再查分数)
    db.Preload("Scores").First(&student, 1)
    log.Printf("学生:%s,分数列表:%+v", student.Name, student.Scores)
}

2、多对多关系(学生 - 课程 通过分数表)

学生和课程是多对多关系,需通过分数表关联,Gorm 支持直接定义多对多关系:

// 给Student结构体补充多对多关联
type Student struct {
    BaseModel
    // ... 原有字段
    Courses []Course `gorm:"many2many:scores;foreignKey:StudentID;joinForeignKey:CourseID" json:"courses"`
}

// 查询学生关联的所有课程
func testMany2Many() {
    var student Student
    // Preload 预加载多对多关联
    db.Preload("Courses").First(&student, 1)
    log.Printf("学生:%s,选修课程:%+v", student.Name, student.Courses)
}

3、Joins 关联查询(连表查询,性能更高)

Preload 是 N+1 查询(先查主表,再查关联表),Joins 是 JOIN 连表查询(一次 SQL),适合简单关联场景:

func testJoins() {
    // 查询学生姓名和对应的课程名、分数
    var result []struct {
        StudentName string  `gorm:"column:student_name"`
        CourseName  string  `gorm:"column:course_name"`
        Score       float64 `gorm:"column:score"`
    }
    db.Table("students")
        .Joins("LEFT JOIN scores ON students.id = scores.student_id")
        .Joins("LEFT JOIN courses ON courses.id = scores.course_id")
        .Select("students.name as student_name, courses.name as course_name, scores.score")
        .Where("students.id = ?", 1)
        .Scan(&result)
    log.Printf("Joins查询结果:%+v", result)
}

4、关联插入

创建学生时,同时插入关联的分数和课程:

func testAssociationCreate() {
    // 创建课程
    course := Course{Name: "英语", Teacher: "李老师", CourseNo: "ENG001"}
    db.Create(&course)

    // 创建学生并关联分数
    student := Student{
        Name:     "孙七",
        Age:      18,
        Class:    "高一(1)班",
        StudentNo: "2024005",
        Scores: []Score{
            {CourseID: course.ID, Score: 88.0},
        },
    }
    // 自动插入学生和分数(关联插入)
    db.Create(&student)
}

5、BeforeCreate 钩子(创建前逻辑)

Gorm 支持生命周期钩子,BeforeCreate 会在创建数据前执行,可用于自动生成学号、设置默认值等:

// 给Student结构体添加BeforeCreate方法
func (s *Student) BeforeCreate(tx *gorm.DB) error {
    // 创建前自动生成学号(格式:2024 + 6位随机数)
    if s.StudentNo == "" {
        import "strconv"
        import "math/rand"
        import "time"
        rand.Seed(time.Now().UnixNano())
        randNum := rand.Intn(999999)
        s.StudentNo = "2024" + strconv.FormatInt(int64(randNum), 10)
    }
    return nil
}

// 测试钩子:创建学生时自动生成学号
func testBeforeCreate() {
    student := Student{
        Name:  "周八",
        Age:   17,
        Class: "高一(2)班",
        // 未指定StudentNo,由BeforeCreate自动生成
    }
    db.Create(&student)
    log.Printf("自动生成学号:%s", student.StudentNo)
}

事务使用

Gorm 支持事务操作,核心是 Begin(开启事务)、Commit(提交)、Rollback(回滚):

func testTransaction() {
    // 开启事务
    tx := db.Begin()
    defer func() {
        // 捕获panic,回滚事务
        if r := recover(); r != nil {
            tx.Rollback()
        }
    }()

    // 事务内操作1:插入学生
    student := Student{Name: "吴九", Age: 18, Class: "高一(3)班"}
    if err := tx.Create(&student).Error; err != nil {
        tx.Rollback() // 失败回滚
        log.Printf("插入学生失败,回滚事务:%v", err)
        return
    }

    // 事务内操作2:插入分数
    score := Score{StudentID: student.ID, CourseID: 1, Score: 90.0}
    if err := tx.Create(&score).Error; err != nil {
        tx.Rollback() // 失败回滚
        log.Printf("插入分数失败,回滚事务:%v", err)
        return
    }

    // 提交事务
    if err := tx.Commit().Error; err != nil {
        tx.Rollback()
        log.Printf("提交事务失败:%v", err)
        return
    }
    log.Println("事务执行成功")
}

Gorm 的核心优势是简洁的 API 和强大的关联能力,掌握以上知识点即可应对大部分业务场景。实际开发中建议结合官方文档,根据业务需求灵活调整配置和查询方式。