概要
就是go语言的一个对于数据库操作的包 让你更加方便的操作数据库 没了
初始化
plain go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite |
|---|
前情提要
约定
主键:GORM 使用一个名为ID 的字段作为每个模型的默认主键。
表名:默认情况下,GORM 将结构体名称转换为 snake_case 并为表名加上复数形式。比如说User 会变成users。GormUserName变成 gorm_user_names
列名:GORM 自动将结构体字段名称转换为 snake_case 作为数据库中的列名。
时间戳字段:GORM使用字段 CreatedAt 和 UpdatedAt 来自动跟踪记录的创建和更新时间。
| 概念 | Go 结构体/字段 | 默认数据库映射 | 规则说明 |
|---|---|---|---|
| 表名 | type User struct |
users |
结构体名转 小写 + 复数 (User -> users)。 |
| 主键 | ID |
id |
字段名为 ID (大小写不限) 默认为主键。 |
| 字段名 | Name, Age |
name, age |
驼峰命名 (CamelCase) 自动转为 蛇形命名 (snake_case)。 |
| 时间戳 | CreatedAt |
created_at |
特定字段名会被识别为自动管理的时间戳。 |
嵌套问题
go
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Password
}
type Password struct {
pas int
salt string
}
像这样的 一个结构体嵌套在另一个结构体中的 这种格式 只需要如上写法 就可以额 实现嵌套 等价于
go
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
pas int
salt string
}
方法二
go
对于正常的结构体字段,你也可以通过标签 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
}
链接
不连接怎么使用呢(笑
go
func main() {
dsn := "root:12345678@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
log.Println("connected to database,", db)
}
账号root 密码12345678
小demo 关于插入一个user
go
package main
import (
"context"
"log"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
Name string
Age int
}
func main() {
dsn := "root:12345678@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
log.Println("connected to database,", db)
user := User{Name: "Jinzhu", Age: 18}
//Create a single record
这个就是只是插入 而不知道结果
ctx := context.Background()
err = gorm.G[User](db).Create(ctx, &user) // pass pointer of data to Create
// 这个是可以通过result 获取到影响行数
result := gorm.WithResult()
err = gorm.G[User](db, result).Create(ctx, &user)
log.Println("user ID:", user.ID) // returns inserted data's primary key
// log.Println("result error:", result.Error) // returns error
log.Println("result rows affected:", result.RowsAffected) // returns inserted records count
}
这种又长又难记的是通用api
通用 API (Generics API / 泛型 API)
是 GORM 为了拥抱 Go 语言新特性(Go 1.18 引入泛型)而推出的现代化写法。这里的"通用"翻译自 Generics(泛型),意指它可以通用地处理任何类型,但同时保持类型安全。
- 核心特征 :
- 显式指定类型 :必须使用
<font style="color:rgb(6, 10, 38);">gorm.G[User]</font>明确告诉编译器我们要操作<font style="color:rgb(6, 10, 38);">User</font>表。 - 直接返回 error :
<font style="color:rgb(6, 10, 38);">Create</font>方法直接返回<font style="color:rgb(6, 10, 38);">error</font>,更符合 Go 语言的标准习惯 (<font style="color:rgb(6, 10, 38);">if err != nil</font>)。 - 显式 Context :
<font style="color:rgb(6, 10, 38);">ctx</font>是<font style="color:rgb(6, 10, 38);">Create</font>函数的第一个必传参数,强制你处理超时和取消逻辑。 - 可选 Result :如果你需要
<font style="color:rgb(6, 10, 38);">RowsAffected</font>,需要额外创建一个<font style="color:rgb(6, 10, 38);">result</font>对象传进去(如你文档所示),否则默认只关心成功与否。
- 显式指定类型 :必须使用
- 代码样子 :go编辑
plain
// 必须写 [User],明确类型
err := gorm.G[User](db).Create(ctx, &user)
// 直接判断 err
if err != nil { ... }
// 如果需要行数,才用 result
// result := gorm.WithResult()
// err := gorm.G[User](db, result).Create(ctx, &user)
- 优点 :
- 类型安全 :编译器能帮你检查类型错误(比如把
<font style="color:rgb(6, 10, 38);">Product</font>传给<font style="color:rgb(6, 10, 38);">User</font>的操作)。 - IDE 提示更准:因为类型明确,自动补全更智能。
- Context 规范:强制传递上下文,适合高并发 Web 服务。
- 无需断言:读取数据时直接得到具体类型,不需要类型转换。
- 类型安全 :编译器能帮你检查类型错误(比如把
- 缺点 :
- 代码稍微长一点点(需要写
<font style="color:rgb(6, 10, 38);">[User]</font>和<font style="color:rgb(6, 10, 38);">ctx</font>)。 - 需要较新的 GORM 版本 (v1.25+) 和 Go 版本 (1.18+)。
- 网上的旧教程可能不涉及这种写法。
- 代码稍微长一点点(需要写
传统 API
- 核心特征 :
- 不指定类型 :调用时不需要告诉 GORM 操作的是
<font style="color:rgb(6, 10, 38);">User</font>还是<font style="color:rgb(6, 10, 38);">Product</font>,它通过你传入的变量指针自动推断。 - 返回对象 :
<font style="color:rgb(6, 10, 38);">db.Create(&user)</font>返回的是一个<font style="color:rgb(6, 10, 38);">*gorm.DB</font>对象(代码中的<font style="color:rgb(6, 10, 38);">result</font>)。 - 获取错误 :需要访问
<font style="color:rgb(6, 10, 38);">result.Error</font>。 - Context 支持 :比较隐式,通常需要先调用
<font style="color:rgb(6, 10, 38);">db.WithContext(ctx)</font>。
- 不指定类型 :调用时不需要告诉 GORM 操作的是
go
// 不需要写 [User]
result := db.Create(&user)
// 错误和结果都在 result 对象里
if result.Error != nil { ... }
count := result.RowsAffected
- 优点 :
- 代码简短,写起来快。
- 兼容所有旧版本 GORM。
- 社区资料最多。
- 缺点 :
- 类型安全性稍弱(依赖反射,编译器无法在写代码时检查你是否传错了结构体)。
- Context 传递有时候容易忘记。
注意 在传统api中不能使用 结构体作为穿惨 二十使用指向结构体的指针
插入
go
db.Select("name", "age").Create(&user)// 插入有的字段
// INSERT INTO `users` (`name`,`age`,) VALUES ("jinzhu", 18)
db.Omit("name", "age").Create(&user)// 插入除有的字段
// INSERT INTO `users` (`location`,`birth`,) VALUES ("zhejiang", 18)
批量插入
go
var users = []User{{Name: "jinzhu1" }, {Name: "jinzhu2" }, {Name: "jinzhu3" }}
db.CreateInBatches(users, 100 )//批次大小为 100 设置批次 可以不写
db.Create(&users)
for _, user := range users {
user.ID // 1,2,3
}
根据 Map 创建
GORM支持通过 map[string]interface{} 与 []map[string]interface{}{}来创建记录。
go
db.Model(&User{}).Create(map[string]interface{}{
"Name": "jinzhu", "Age": 18,
})
// batch insert from []map[string]interface{}{}
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "jinzhu_1", "Age": 18},
{"Name": "jinzhu_2", "Age": 20},
})
注意当使用map来创建时,钩子方法不会执行,关联不会被保存且不会回写主键。
钩子🪝函数
顾名思义 在执行前中后 的时候 会执行的函数
go
// 定义一个方法,绑定在 User 结构体上
// 方法名必须是 BeforeCreate,参数必须是 *gorm.DB,返回值必须有 error
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
// 【动作 1】在保存之前,自动生成一个 UUID
// 当你调用 db.Create(&user) 时,哪怕你没给 UUID 赋值,
// GORM 会在写入数据库前,先运行这行代码,把 UUID 填好。
u.UUID = uuid.New()
// 【动作 2】在保存之前,进行业务逻辑校验
if u.Role == "admin" {
// 如果角色是 admin,返回一个错误
// 一旦返回错误,GORM 会立即停止!
// 数据【不会】被写入数据库,整个 Create 操作失败。
return errors.New("invalid role")
}
// 如果没有问题,返回 nil (err 默认为 nil),GORM 继续执行插入操作
return
}
这个钩子是如何工作的?(生命周期)
假设你在 <font style="color:rgb(6, 10, 38);">main</font> 函数里写了这样一行代码:
plain
user := User{Name: "Jinzhu", Role: "admin"}
err := db.Create(&user)
没有钩子时:
- GORM 接收
<font style="color:rgb(6, 10, 38);">user</font>。 - GORM 生成
<font style="color:rgb(6, 10, 38);">INSERT INTO users ...</font>SQL。 - 发送给数据库。
- 结束。
有了**** **<font style="color:rgb(6, 10, 38);">BeforeCreate</font>******钩子后:
- GORM 接收
<font style="color:rgb(6, 10, 38);">user</font>。 - ⏸️ GORM 暂停 。它发现
<font style="color:rgb(6, 10, 38);">User</font>结构体有一个叫<font style="color:rgb(6, 10, 38);">BeforeCreate</font>的方法。 - 🏃 GORM 自动执行你的****
**<font style="color:rgb(6, 10, 38);">BeforeCreate</font>******方法 :- 它把
<font style="color:rgb(6, 10, 38);">u.UUID</font>填上了新值。 - 它检查
<font style="color:rgb(6, 10, 38);">u.Role</font>,发现是<font style="color:rgb(6, 10, 38);">"admin"</font>。 - 它执行
<font style="color:rgb(6, 10, 38);">return errors.New(...)</font>。
- 它把
- ⚠️ GORM 检测到错误 :因为钩子返回了 error,GORM 决定中止后续操作。
- ❌ 不发送 SQL:数据没有写入数据库。
- 🔙 返回错误 :你的
<font style="color:rgb(6, 10, 38);">db.Create(&user)</font>收到那个 "invalid role" 错误。
查询
go
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
加上条件
String条件
比较low 有点像原生的slq语句
go
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
这边就展示这两个 因为大差不差 就是 占位符+原生sql那一套
struct/map条件
go
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
使用结构体 看起来比较nb一点
选择条件
go
or
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
排序
go
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
Limit & offset
go
db.Limit(10).Find(&user).Limit(-1).Find(&user)
db.Offset(10).Find(&user)
limit(-1) 是为了消除limit条件的 在链式查询中
Distinct
go
db.Distinct("name", "age").Order("name, age desc").Find(&results)
Scan
这是查询以后 将结果返还给 结构体
go
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)