当前位置: 首页 > news >正文

Gorm学习笔记 - 概述

资料地址:https://www.kancloud.cn/sliver_horn/gorm/1861152

概述

关联 (Has One、Has Many、Belongs To、Many To Many、多态、单表继承)
Create、Save、Update、Delete、Find 前/后的勾子
基于Preload、Joins的预加载
事务、嵌套事务、保存点、回滚至保存点
Context、Prepared Statment 模式、DryRun 模式
批量插入、FindInBatches、查询至 Map
SQL Builder, Upsert, Locking, Optimizer/Index/Comment Hints
复合主键
自动迁移
自定义 Logger
灵活的可扩展插件 API:Database Resolver(读写分离)、Prometheus

先把这些概念理解一遍,再对要点进行逐章学习记录

一、关联关系

在 ORM (对象关系映射) 中,关联关系是描述不同数据模型之间关系的核心概念。以下是各种关联关系的详细解释:

1. Has One (一对一)

表示一个模型拥有另一个模型,是一种一对一关系。
​示例​:

type User struct {gorm.ModelCreditCard CreditCard
}type CreditCard struct {gorm.ModelNumber stringUserID uint // 外键
}

​特点​:
一个用户只能拥有一张信用卡
外键存在于被拥有的模型中(CreditCard中的UserID)

2. Has Many (一对多)

表示一个模型拥有多个另一个模型的实例。
​示例​:

type User struct {gorm.ModelOrders []Order
}type Order struct {gorm.ModelUserID uint // 外键Amount float64
}

​特点​:
一个用户可以拥有多个订单
外键存在于被拥有的模型中(Order中的UserID)

3. Belongs To (属于)

表示一个模型属于另一个模型,是Has One/Has Many的反向关系。
​示例​:

type Order struct {gorm.ModelUser   UserUserID uint // 外键
}

​特点​:
外键存在于当前模型中
表示从属关系,如订单属于用户

4. Many To Many (多对多)

表示两个模型之间可以相互拥有多个实例。
​示例​:

type User struct {gorm.ModelLanguages []Language `gorm:"many2many:user_languages;"`
}type Language struct {gorm.ModelName stringUsers []User `gorm:"many2many:user_languages;"`
}

​特点​:
需要一个中间表(user_languages)来存储关联关系
一个用户可以说多种语言,一种语言可以被多个用户使用

5. 多态关联

允许一个模型属于多个其他模型,通过一个接口实现。
​示例​:

type Comment struct {gorm.ModelContent     stringCommentableID   uintCommentableType string
}type Post struct {gorm.ModelTitle    stringComments []Comment `gorm:"polymorphic:Commentable;"`
}type Video struct {gorm.ModelTitle    stringComments []Comment `gorm:"polymorphic:Commentable;"`
}

​特点​:
评论可以属于帖子或视频
通过CommentableType和CommentableID字段确定关联

6. 单表继承

将多个模型存储在同一个数据库表中,通过类型字段区分。
​示例​:

type Product struct {gorm.ModelName       stringType       string `gorm:"type:varchar(20)"` // "Book", "Movie", etc.// Book specific fieldsAuthor     stringPages      int// Movie specific fieldsDirector   stringDuration   int
}

​特点​:
所有子类共享同一个表
通过Type字段区分不同类型
某些字段可能只对特定类型有意义

gorm在关联的时候,会有一些约定的字段写法,需要注意,这些并不是实际字段,这些字段后面,往往有一些修饰的gorm:的定义


二、Preload 和 Joins 的预加载机制

1. Preload 预加载

Preload是 GORM 中最常用的预加载方法,它通过额外的查询(通常是 IN 查询)来加载关联数据。
基本用法

// 查找用户并预加载其订单
db.Preload("Orders").Find(&users)// 预加载嵌套关联
db.Preload("Orders.OrderItems").Find(&users)// 带条件的预加载
db.Preload("Orders", "state = ?", "paid").Find(&users)

工作原理
首先执行主查询(查找 users)
然后执行额外的查询(查找与这些 users 关联的 orders)
最后将结果组装到相应的数据结构中

2. Joins 预加载

Joins预加载则使用 SQL JOIN 语句一次性获取主表和关联表的数据。

// 使用 JOIN 预加载
db.Joins("Company").Find(&users)// 带条件的 JOIN 预加载
db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users)

工作原理
构建一个包含 JOIN 的 SQL 查询
一次性获取主表和关联表的数据
将结果映射到对应的数据结构

两种方式的比较
特性PreloadJoins
查询方式多个独立查询单个 JOIN 查询
性能适合少量主记录+大量关联记录适合大量主记录+少量关联记录
灵活性可对每个关联单独设置条件整个查询只能有一套条件
结果处理自动组装嵌套结构需要处理可能的重复数据
适用场景复杂关联结构简单关联且需要过滤或排序关联数据

三、事务管理机制

1. 事务 (Transaction)

事务是数据库操作的基本单位,它遵循ACID原则:
​原子性(Atomicity)​​:事务中的所有操作要么全部完成,要么全部不完成
​一致性(Consistency)​​:事务执行前后,数据库从一个一致状态变为另一个一致状态
​隔离性(Isolation)​​:并发事务之间互不干扰
​持久性(Durability)​​:事务一旦提交,其结果就是永久性的
​在ORM中的使用示例​:

tx := db.Begin()// 在事务中执行操作
if err := tx.Create(&user).Error; err != nil {// 出错时回滚tx.Rollback()return err
}// 提交事务
tx.Commit()
2. 嵌套事务 (Nested Transaction)

嵌套事务是指在一个事务内部开启另一个事务,形成事务的层级结构。在大多数数据库中,实际上并不支持真正的嵌套事务,但ORM可以通过保存点(Savepoint)来模拟这种行为。
​特点​:
内层事务的提交不会真正提交到数据库,只有最外层事务的提交才会生效
内层事务的回滚只会回滚到该事务开始时的状态,不影响外层事务
外层事务的回滚会导致所有内层事务的操作都被回滚

​示例​:

tx1 := db.Begin()// 第一个操作
tx1.Create(&user1)// 嵌套事务开始
tx2 := tx1.Begin()
tx2.Create(&user2)
if someCondition {tx2.Rollback() // 只回滚tx2的操作
} else {tx2.Commit()
}// 提交最外层事务
tx1.Commit()
3. 保存点 (Savepoint) 、回滚至保存点 (Rollback to Savepoint)

保存点是事务中的一个标记点,可以用于部分回滚。它允许你回滚到事务中的某个特定点,而不是回滚整个事务。
​特点​:
可以在事务中设置多个保存点
可以回滚到指定的保存点,而不影响保存点之前的操作
保存点在事务提交或回滚后自动释放

​示例​:

tx := db.Begin()// 初始操作
tx.Create(&user1)// 设置保存点
tx.SavePoint("before_user2")// 尝试操作
tx.Create(&user2)
tx.Create(&user3)if someError {// 回滚到保存点,撤销user2和user3的创建tx.RollbackTo("before_user2")// 可以尝试其他操作tx.Create(&user4)
}// 提交事务
tx.Commit()

四、Context、Prepared Statement 模式​ 和 ​DryRun 模式​

1. ​Context​

​作用​:
允许在数据库操作中传递 context.Context,用于控制超时、取消操作或传递请求范围的元数据(如链路追踪的 trace_id)。
​使用场景​:
​超时控制​:避免数据库操作长时间阻塞。
​取消操作​:例如用户取消请求时,终止关联的数据库查询。
​链路追踪​:在微服务中传递上下文信息(如日志、监控)。

​示例​:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()// 将 ctx 传递给 GORM 操作
var user User
db.WithContext(ctx).First(&user, 1)
2. ​Prepared Statement 模式​

​作用​:
启用后,GORM 会缓存预编译的 SQL 语句(Prepared Statements),提升重复查询的性能(特别是批量操作)。
​原理​:
首次执行 SQL 时,数据库会编译 SQL 并生成执行计划。
后续相同结构的 SQL 直接复用编译结果,减少解析开销。
​使用场景​:
高频执行的相同查询(如批量插入、循环更新)。
对性能敏感的场景。

​示例​:

// 全局启用(所有操作)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{PrepareStmt: true,
})// 或单次启用
tx := db.Session(&gorm.Session{PrepareStmt: true})
tx.First(&user, 1) // 此查询会复用预编译语句
3. ​DryRun 模式​

​作用​:
只生成 SQL 但不实际执行,用于调试或验证 SQL 的正确性。
​使用场景​:
开发阶段检查 GORM 生成的 SQL 是否符合预期。
日志记录或审计 SQL 语句。
避免测试时污染数据库。

​示例​:

// 全局启用(所有操作)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{DryRun: true,
})// 或单次启用
tx := db.Session(&gorm.Session{DryRun: true})
tx.Create(&user) // 仅打印 SQL,不执行
// 输出:INSERT INTO users (...) VALUES (...)

​获取生成的 SQL​:

stmt := db.Session(&gorm.Session{DryRun: true}).First(&user, 1).Statement
sql := stmt.SQL.String()  // 获取生成的 SQL
vars := stmt.Vars         // 获取参数
三者的区别总结
特性主要用途适用场景
​Context​控制超时、取消、传递元数据超时控制、链路追踪、取消操作
​​Prepared Statement​提升重复查询性能高频相同查询(批量操作、循环)
​​DryRun​​调试 SQL 不实际执行开发调试、SQL 审计、测试避免污染

五、批量插入、FindInBatches、查询至 Map

1. 批量插入 (Batch Insert)

批量插入是指一次性将多条记录插入数据库,而不是逐条插入,这可以显著提高插入性能。
​使用场景​:
需要插入大量数据时
需要提高数据插入效率时
​示例代码​:

var users = []User{{Name: "Alice"}, {Name: "Bob"}, {Name: "Charlie"}}// 批量插入
result := db.Create(&users)// 分批插入(每批100条)
result := db.CreateInBatches(&users, 100)

​优点​:
减少数据库连接次数
提高插入速度
减少网络开销

2. FindInBatches

FindInBatches 用于分批查询大量数据,避免一次性加载所有数据到内存中。
​使用场景​:
处理大量数据时避免内存溢出
需要对大数据集进行批处理时
​示例代码​:

// 每批处理100条记录
db.Where("age > ?", 18).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {for _, result := range results {// 处理每条记录}return nil
})

​优点​:
避免一次性加载大量数据导致内存不足
可以分批处理数据,适合大数据场景

可以在每批处理中添加业务逻辑

  1. 查询至 Map (Scan/Map)
    查询至 Map 是指将查询结果直接映射到 map 或自定义结构体,而不是预定义的模型。
    ​使用场景​:
    只需要查询部分字段时
    查询结果不符合现有模型结构时
    需要动态处理查询结果时

​示例代码​:

// 查询到 map
var result map[string]interface{}
db.Model(&User{}).First(&result)// 查询到自定义结构体
type UserDTO struct {Name stringAge  int
}
var userDTO UserDTO
db.Model(&User{}).First(&userDTO)// 查询多行到 map 切片
var results []map[string]interface{}
db.Model(&User{}).Find(&results)

​优点​:
灵活性高,不依赖预定义模型
可以只查询需要的字段,减少数据传输量
适合动态查询和结果处理

总结对比
功能主要用途适用场景性能考虑
批量插入高效插入多条数据数据迁移、初始化数据大幅提高插入性能
FindInBatches分批处理大量数据大数据处理、批处理任务避免内存溢出
查询至 Map灵活处理查询结果动态查询、自定义结果处理减少不必要的数据传输

六、SQL Builder、Upsert、 Locking、Optimizer/Index/Comment Hints

1.SQL Builder

GORM 提供了强大的 SQL 构建能力,允许你以链式调用的方式构建复杂的 SQL 查询。

// 基本查询构建
db.Where("name = ?", "jinzhu").Where("age >= ?", 18).Find(&users)// 复杂条件
db.Where("name <> ? AND age > ?", "jinzhu", 20).Find(&users)// 选择特定字段
db.Select("name", "age").Find(&users)// 排序
db.Order("age desc, name").Find(&users)// 分组
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Find(&results)
2.Upsert

Upsert 是 “update or insert” 的缩写,用于在记录存在时更新,不存在时插入。

// 使用 Save 方法实现 Upsert
db.Save(&user) // 根据主键存在与否决定插入或更新// 使用 Clauses 实现更灵活的 Upsert
db.Clauses(clause.OnConflict{Columns:   []clause.Column{{Name: "id"}}, // 冲突键DoUpdates: clause.AssignmentColumns([]string{"name", "age"}), // 更新哪些字段
}).Create(&user)// 使用 OnConflict 指定更新行为
db.Clauses(clause.OnConflict{UpdateAll: true, // 冲突时更新所有字段
}).Create(&users)
3.Locking

锁机制用于控制并发访问,GORM 支持行级锁。

// 悲观锁 - SELECT FOR UPDATE
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)// 共享锁 - SELECT FOR SHARE
db.Clauses(clause.Locking{Strength: "SHARE"}).Find(&users)// 指定锁模式
db.Clauses(clause.Locking{Strength: "UPDATE",Options: "NOWAIT", // 如果锁不可立即获取则返回错误
}).Find(&users)
4.Optimizer/Index/Comment Hints

这些提示用于影响查询优化器的行为。

索引提示

// 强制使用特定索引
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})// 忽略特定索引
db.Clauses(hints.IgnoreIndex("idx_user_name")).Find(&User{})// 强制索引连接顺序
db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_age").ForJoin()).Find(&User{})

优化器提示

// 设置优化器提示
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{}) // 设置最大执行时间10秒// MySQL 特定的优化器提示
db.Clauses(hints.New("/*+ MRR(user) */")).Find(&User{})

查询注释

// 添加查询注释
db.Clauses(hints.Comment("select from master")).Find(&User{})// 带条件的注释
db.Clauses(hints.CommentBefore("select", "from master")).Find(&User{})

综合示例

// 复杂查询示例
db.Clauses(hints.UseIndex("idx_age"),hints.Comment("获取活跃用户"),clause.Locking{Strength: "UPDATE"},
).Where("age > ?", 18).Order("last_active_at desc").Limit(100).Find(&activeUsers)// Upsert 并获取执行结果
result := db.Clauses(clause.OnConflict{Columns:   []clause.Column{{Name: "email"}},DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&user)// 检查受影响的行数
if result.RowsAffected == 0 {// 记录已存在并被更新
}

七、复合主键

复合主键是指由多个字段组合构成的主键,而不是仅由单个字段作为主键。在数据库设计中,当一个表需要由两个或多个字段共同唯一标识一条记录时,就会使用复合主键。

  • 在GORM中,可以通过结构体标签 gorm:"primaryKey"为多个字段标记复合主键:
type Product struct {ID           string `gorm:"primaryKey"`LanguageCode string `gorm:"primaryKey"`Name         stringPrice        float64
}

八、Database Resolver(读写分离)、Prometheus(插件)、灵活的可扩展插件 API

在GORM框架中,"灵活的可扩展插件API"指的是GORM提供了一套机制,允许开发者通过插件方式扩展框架功能。其中两个重要的插件示例是Database Resolver(用于读写分离)和Prometheus(用于监控)。

1. Database Resolver(读写分离)

Database Resolver插件主要用于实现数据库的读写分离功能:
​读写分离​:将读操作(SELECT)和写操作(INSERT/UPDATE/DELETE)路由到不同的数据库实例
​多数据库支持​:可以配置多个读库和写库
​负载均衡​:在读库之间进行负载均衡
​故障转移​:当某个数据库实例不可用时自动切换到其他实例
示例配置:

db.Use(dbresolver.Register(dbresolver.Config{Sources:  []gorm.Dialector{mysql.Open("write_db_dsn")}, // 写库Replicas: []gorm.Dialector{mysql.Open("read_db_dsn")},  // 读库Policy:   dbresolver.RandomPolicy{},                   // 读库选择策略
}))
2. Prometheus 插件

Prometheus插件用于收集和暴露GORM的数据库操作指标:
​监控指标​:收集SQL查询数量、执行时间、错误率等
​Prometheus集成​:将收集的指标暴露给Prometheus监控系统
​性能分析​:帮助开发者识别慢查询和性能瓶颈
示例配置:

import "gorm.io/plugin/prometheus"db.Use(prometheus.New(prometheus.Config{DBName:          "my_db",       // 数据库名标识RefreshInterval: 15,            // 指标刷新间隔(秒)PushAddr:        "prometheus pusher address", // Prometheus推送地址StartServer:     true,          // 是否启动HTTP服务器暴露指标HTTPServerPort:  8080,          // HTTP服务器端口
}))
3. 插件API的灵活性

GORM的插件API设计允许开发者:
轻松集成第三方插件
开发自定义插件扩展功能
通过钩子函数在数据库操作的生命周期中插入自定义逻辑
根据需求组合不同的插件
这种设计使得GORM能够保持核心简洁,同时通过插件机制满足各种复杂场景的需求。

http://www.dtcms.com/a/442002.html

相关文章:

  • wordpress 双分页北京朝阳区优化
  • 7-1 社会工程学攻击
  • 浪浪山 iOS 奇遇记:给 APP 裹上 Liquid Glass “琉璃罩”(下集)
  • Leetcode 215. 数组中的第K个最大元素 快速排序 / 堆排序
  • 网站建设排名奇艺地域邢台建设一个企业网站
  • 电子商务网站建设员网站建设维护文档
  • QT肝8天18--用户角色管理
  • 【开题答辩实录分享】以《基于Python的新能源汽车管理系统的设计与实现》为例进行答辩实录分享
  • springboot+vue智慧旅游管理小程序(源码+文档+调试+基础修改+答疑)
  • [创业之路-683]:“行业的分类”
  • MCI评估量表
  • 探索 Docker/K8s 部署 MySQL 的创新实践与优化技巧——高可用与性能调优进阶
  • Coze源码分析-资源库-编辑知识库-前端源码-核心组件
  • 搜索网站排名mj wordpress
  • 网站容量空间一般要多大做装修效果图的网站
  • MySQL-表的操作
  • C++架构全解析:从设计哲学到实战应用
  • wordpress 多级导航网络营销优化外包
  • 视频生成技术Deepfake
  • 【大语言模型 82】LoRA高级技巧:秩选择与初始化策略
  • 自己做的网站百度搜不到网站的空间是
  • Leetcode 3698. Split Array With Minimum Difference
  • mysql学习--日志查询
  • 北京做网站哪家强网站被k如何恢复
  • Redis的零食盒满了怎么办?详解缓存淘汰策略
  • display mac-address vlan vlan-id 概念及题目
  • 国内十大网站建设广州11个区排名
  • windows远程桌面连接的时候用户名用什么
  • Webpack实战笔记:从自动构建到本地服务器搭建的完整流程
  • SpringBoot + MongoDB全栈实战:从架构原理到AI集成