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 提供了丰富的更新方式,覆盖单字段、多字段、条件更新、批量更新等场景,核心方法包括 Save、Update、Updates,需重点注意零值更新和字段选择问题。
更新操作核心:
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 和强大的关联能力,掌握以上知识点即可应对大部分业务场景。实际开发中建议结合官方文档,根据业务需求灵活调整配置和查询方式。