GORM实践 | 青训营笔记
这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记
特性
- 全功能 ORM
- 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持
Preload、Joins的预加载 - 事务,嵌套事务,Save Point,Rollback To Saved Point
- Context、预编译模式、DryRun 模式
- 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- Auto Migration
- 自定义 Logger
- 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
- 每个特性都经过了测试的重重考验
- 开发者友好
连接到数据库
GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server
MySQL
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
MySQl 驱动程序提供了 一些高级配置 可以在初始化过程中使用,例如:
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{})
GORM 使用database/sql维护连接池
sqlDB, err := db.DB()
// 空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)
// 打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)
// 连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)
基本用法
GORM 模型声明
- 表名为struct name的snake_cases (下划线连接)复数格式
- 字段名为field name的snake_case单数格式
- ID/Id字段为主键,如果为数字,则为自增主键
- CreatedAt 字段,创建时,保存当前时间
- UpdatedAt 字段,创建、更新时,保存当前时间
- gorm.DeletedAt 字段,默认开启 soft delete 模式
GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
对于匿名字段,GORM 会将其字段包含在父结构体中,例如:
type User struct {
gorm.Model
Name string
}
// 等效于
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入,例如:
type Author struct {
Name string
Email string
}
type Blog struct {
ID int
Author Author `gorm:"embedded"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}
并且,您可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀,例如:
type Blog struct {
ID int
Author Author `gorm:"embedded;embeddedPrefix:author_"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
AuthorName string
AuthorEmail string
Upvotes int32
}
GORM增删改查
点击查看代码🐋
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Userinfo struct {
Id uint
Name string
Gender string
Hobby string
}
func main() {
// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err1)
}
//连接池
sqlDB, err := db.DB()
if err != nil {
panic(err)
}
// 空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)
// 打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)
// 连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)
//自动迁移
//创建1
db.AutoMigrate(&Userinfo{})
u1 := Userinfo{Id: 1, Name: "张三", Gender: "男", Hobby: "学习"}
db.Create(&u1)
//创建2: 创建记录并更新给出的字段
u2 := Userinfo{Id: 1, Name: "李四", Gender: "男", Hobby: "游戏"}
db.Select("Name", "Age", "CreatedAt").Create(&u2)
//创建3: 创建记录且一同忽略传递给略去的字段值
u3 := Userinfo{Id: 1, Name: "王五", Gender: "男", Hobby: "吃饭"}
db.Select("Name", "Age", "CreatedAt").Create(&u3)
//批量插入
var users = []Userinfo{{Name: "张三"}, {Name: "李四"}, {Name: "王五"}}
db.Create(&users)
var users = []User{{name: "张三_1"}, ....,{Name: "张三_10000"}}
// 数量为 100
db.CreateInBatches(users, 100)
// 获取第一条记录(主键升序)
db.First(&user)
// 获取一条记录,没有指定排序字段
db.Take(&user)
// 获取最后一条记录(主键降序)
db.Last(&user)
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
// 更新
db.First(&user)
user.Name = "张三"
user.Age = 100
db.Save(&user)
db.Delete(&email)
// 带条件的删除
db.Where("name = ?", "张三").Delete(&email)
}
GORM设计原理
应用程序——>操作接口——>database/sql——>连接/操作接口——>数据库
-
Gorm finisher执行GORM statement
-
SQL是怎么生成的——扩展子句
-
SQL是怎么生成的——选择子句
-
GORM插件的工作:注册、移动、替代、获取、before
-
多数据库、读写分离: 负载均衡策略
-
ConnPool: GORM——>写DB/读DB
- 缓存操作
- 预编译操作
- 数据安全起见:GORM——SQL>ConnPool——插件>数据库
-
最开始的问题:链接Mysql
- Dialector:GORM生成
- Options接口
GORM最佳实践
-
数据序列化与SQL表达式:
- gorm.Expr
- struct定义GormValuer
- 自定义查询SQL接口
-
批量数据操作——批量操作、查询:
- 批量创建
- 批量查询
-
批量数据加速操作:
- 关闭默认事务
- 默认批量导入会调用HOOKS方法
- 使用Prepared Statement
-
代码复用、分库分表、sharding——sharding:
-
混沌工程、压力测试:检查系统状态能不能发现某一错误。
-
logger、trace
-
Gen代码生成/RAW-SQL
-
安全问题:
- 安全:以参数传入数据
- 危险:sql注入数据
Gorm-gen
基于 GORM, 更安全更友好的ORM工具 Gorm-gen
- 自动生成 CRUD 和 DIY 方法
- 自动根据表结构生成模型(model)代码
- 事务、嵌套事务、保存点、回滚事务点
- 完全兼容 GORM
- 更安全、更友好
- 多种生成代码模式