欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > GORM 中 CURD 操作用法详解

GORM 中 CURD 操作用法详解

2025/4/5 21:53:55 来源:https://blog.csdn.net/qq_73092699/article/details/147000542  浏览:    关键词:GORM 中 CURD 操作用法详解

基本用法

Take

一、基础用法

1. 单独使用 Take
var user User
result := db.Take(&user) 
// SQL: SELECT * FROM users LIMIT 1;
  • 作用:从users表中随机取一条记录(实际取决于数据库引擎的默认行为,可能返回物理存储顺序的第一条)。
  • 返回值:
    • result.RowsAffected:查询到的记录数(0 或 1)。
    • result.Error:若未找到记录,返回gorm.ErrRecordNotFound错误。
2. 结合 Where 条件
var user User
db.Where("age > ?", 20).Take(&user)
// SQL: SELECT * FROM users WHERE age > 20 LIMIT 1;
  • 作用:从满足 age > 20 的记录中随机取一条。
  • 注意:若Where条件无匹配记录,Take会返回ErrRecordNotFound

二、进阶用法

1. 指定查询字段(Select
var user User
db.Select("name", "email").Take(&user)
// SQL: SELECT name, email FROM users LIMIT 1;
  • 场景:仅需部分字段时,减少数据传输量。
2. 结合 ModelTable 方法
// 使用 Model 指定模型
db.Model(&User{}).Take(&user)// 手动指定表名
db.Table("users").Take(&user)
  • 适用性:动态表名或未定义模型结构时使用。
3. 批量查询中的 Take
var users []User
db.Limit(5).Find(&users) // 取 5 条记录
db.Take(&users[0])       // 从结果集中取第一条(非数据库层面)
  • 注意:Take作用于查询结果,而非数据库直接操作。

三、场景示例

1. 快速抽样检查
// 随机抽取一个活跃用户
var activeUser User
db.Where("status = 'active'").Take(&activeUser)
  • 用途:数据抽样或快速预览时,无需关心顺序。
2. 避免全表扫描的优化
// 使用索引字段优化 Take 查询
db.Where("id >= (SELECT FLOOR(MAX(id) * RAND()) FROM users)").Take(&user)
  • 说明:通过子查询模拟随机抽样,减少全表扫描风险。
3. 错误处理
if err := db.Take(&user).Error; err != nil {if errors.Is(err, gorm.ErrRecordNotFound) {fmt.Println("未找到记录")} else {fmt.Println("查询错误:", err)}
}
  • 必要性:显式处理无记录或数据库错误。

四、与 First/Last 的对比

方法SQL 示例排序规则性能影响
TakeSELECT * FROM users LIMIT 1无排序可能全表扫描(随机)
FirstSELECT * FROM users ORDER BY id按主键升序取第一条使用索引(通常高效)
LastSELECT * FROM users ORDER BY id DESC按主键降序取第一条使用索引(通常高效)
  • 关键区别:Take不强制排序,结果可能不稳定;First/Last依赖主键排序,结果确定。

五、注意事项

  1. 性能风险:无条件的Take可能触发全表扫描,大表查询需谨慎。

  2. 结果不确定性:不同数据库实现可能导致返回不同记录(如 MySQL 默认按物理存储顺序)。

  3. Limit(1) 的区别:

    db.Limit(1).Find(&user) // 等效于 Take,但不会返回 ErrRecordNotFound
    
    • TakeLimit(1)的语法糖,但包含错误处理。

Find

一、基本用法

1. 查询所有记录
var users []User
db.Find(&users)
// SQL: SELECT * FROM users;
  • 作用:查询users表的所有记录,结果存入users切片。
  • 注意:若接收参数为非切片类型(如var user User),仅返回第一条记录。
2. 条件查询
// 查询年龄大于20的用户
db.Where("age > ?", 20).Find(&users)
// SQL: SELECT * FROM users WHERE age > 20;
  • 链式操作:支持与WhereSelectOrder等方法组合,细化查询条件。

二、接收结果的类型

1. 结构体或结构体切片
// 接收单条记录(自动取第一条)
var user User
db.Find(&user)// 接收多条记录
var users []User
db.Find(&users)
  • 区别:非切片类型仅接收第一条结果,切片类型接收所有结果。
2. Map 类型
var result map[string]interface{}
db.Table("users").Find(&result)  // 需显式指定表名
  • 要求:使用map接收结果时,必须通过ModelTable指定表名,否则报错。

三、高级功能

1. 预加载关联数据
type User struct {gorm.ModelOrders []Order `gorm:"foreignKey:UserID"`
}
var users []User
db.Preload("Orders").Find(&users)

作用:通过 Preload 加载关联表(如 orders)的数据,避免 N+1 查询问题。

2. 原生 SQL 查询
db.Raw("SELECT * FROM users WHERE id = ?", 1).Scan(&user)
  • 扩展性:支持直接编写 SQL 语句,并通过Scan将结果映射到结构体。

四、与 First/Take 的区别

方法返回结果错误处理SQL 示例
Find所有匹配记录(切片)无结果时不报错SELECT * FROM users
First按主键排序后的第一条无结果时返回 RecordNotFoundSELECT * FROM users ORDER BY id LIMIT 1
Take随机一条记录(无排序)无结果时返回 RecordNotFoundSELECT * FROM users LIMIT 1
  • 关键点:Find不强制排序且无结果时不报错,适合批量查询;First/Take适合单条记录且需错误处理。

五、性能与注意事项

  1. 全表扫描风险:未指定条件时,Find会执行全表查询,大表需谨慎。

  2. 软删除影响:若模型启用软删除(含DeletedAt字段),Find会自动过滤已删除记录;需用Unscoped查询全部数据:

    db.Unscoped().Find(&users) // 包含软删除的记录
    
  3. 调试 SQL:可通过ToSQL方法查看生成的 SQL 语句:

    sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Find(&users) })
    

六、综合示例

// 查询活跃用户的名字和邮箱,按年龄降序排列
db.Select("name", "email").Where("status = ?", "active").Order("age DESC").Find(&users)// 使用 Map 接收结果(需指定表)
var result map[string]interface{}
db.Table("users").Where("id = ?", 1).Find(&result)

通过灵活组合 Find 的条件和方法,可以实现从简单到复杂的数据查询需求。

First

一、核心机制

  1. 排序规则
    First 默认按主键升序​(ORDER BY id ASC)检索第一条记录。若模型未定义主键,则按模型第一个字段排序(如 Code 字段)。

    // 示例:获取 users 表中主键最小的记录
    var user User
    db.First(&user) 
    // SQL: SELECT * FROM users ORDER BY id ASC LIMIT 1
    
  2. 主键依赖性

    • 若主键为自增数字类型(如ID),First返回最早插入的记录。
    • 若主键是字符串(如 UUID),则按字符串的自然顺序排序。
  3. 条件过滤
    支持通过 WhereModel 添加额外条件,增强查询精确性:

    // 获取年龄大于 30 的第一条用户记录
    db.Where("age > ?", 30).First(&user)
    // SQL: SELECT * FROM users WHERE age > 30 ORDER BY id ASC LIMIT 1
    

二、错误处理

  1. 记录不存在
    若未找到记录,First 返回 gorm.ErrRecordNotFound 错误。可通过以下方式处理:

    if errors.Is(db.First(&user).Error, gorm.ErrRecordNotFound) {fmt.Println("记录不存在")
    }
    
  2. 替代方案
    使用 Find + Limit(1) 避免错误(返回空结果而非报错):

    var users []User
    db.Limit(1).Find(&users)
    if len(users) == 0 {fmt.Println("记录不存在")
    }
    

三、性能优化

  1. 索引依赖
    First 依赖主键索引实现高效查询。若主键无序或无索引,可能触发全表扫描。

  2. 显式指定排序字段
    建议通过 Order 显式指定排序字段,避免依赖默认行为:

    db.Order("created_at ASC").First(&user)  // 按时间字段升序取第一条
    

四、适用场景

场景说明
获取最早记录如日志表中最早插入的条目
主键精确查询已知主键值时的快速检索(如 db.First(&user, 1)
条件过滤下的首条记录结合 Where 筛选特定条件下的第一条数据(如最新未读消息)

五、与其他方法的对比

方法排序规则错误处理适用场景
First主键升序返回 ErrRecordNotFound需有序结果的首条记录
Take无显式排序无错误(返回空结果)快速取任意一条记录
Last主键降序返回 ErrRecordNotFound需有序结果的最后一条

六、注意事项

  1. 主键定义
    确保模型明确定义主键字段(如 gorm.Model 包含 ID),避免依赖隐式字段排序。

  2. 并发安全
    高并发场景下,若主键生成策略非自增(如 UUID),First 的排序结果可能不稳定。

  3. 零值处理
    First 不会忽略零值字段,需通过 SelectOmit 显式过滤。


通过合理使用 First,可在有序数据场景中快速定位目标记录,同时需注意主键定义和索引优化以提升性能。

Last

一、核心机制

  1. 排序规则
    Last 默认按主键降序​(ORDER BY id DESC)检索第一条记录。若模型未定义主键,则按模型第一个字段降序排序。

    示例:

    var user User
    db.Last(&user)  // SQL: SELECT * FROM users ORDER BY id DESC LIMIT 1
    
  2. 主键依赖性

    • 若主键为自增数字(如ID),Last返回最新插入的记录(即主键最大值);
    • 若主键为字符串(如 UUID),按自然顺序降序取第一条。
  3. 条件过滤
    支持通过 Where 添加额外条件,但排序仍以主键为核心:

    db.Where("age > ?", 30).Last(&user)  
    // SQL: SELECT * FROM users WHERE age > 30 ORDER BY id DESC LIMIT 1
    

二、错误处理

  • 记录不存在时:Last返回gorm.ErrRecordNotFound错误。可通过errors.Is判断:

    if errors.Is(db.Last(&user).Error, gorm.ErrRecordNotFound) {fmt.Println("记录不存在")
    }
    
  • 替代方案:使用Find+Limit(1)+ 显式排序避免错误:

    var users []User
    db.Order("id DESC").Limit(1).Find(&users)
    if len(users) > 0 {user = users[0]
    }
    

三、性能与优化

  1. 索引依赖

    • Last依赖主键索引高效定位末尾记录。若主键无序或表过大,可能触发全表扫描;

    • 优化建议:添加时间字段索引并显式排序:

      db.Order("created_at DESC").Last(&user)
      
  2. 无主键风险
    若模型未定义主键且首字段无索引,Last 可能性能低下。建议显式定义主键。


四、与其他方法对比

方法排序规则SQL 生成适用场景性能特点
Last主键降序ORDER BY id DESC LIMIT 1获取最新记录(如最新订单)依赖主键索引
First主键升序ORDER BY id ASC LIMIT 1获取最早记录高效(索引扫描)
Take无显式排序LIMIT 1快速取任意一条记录随机扫描

五、使用建议

  1. 明确主键定义:确保模型包含主键字段(如gorm.ModelID)。
  2. 场景选择:
    • 需获取最新数据时优先用Last(如日志末尾条目);
    • 需自定义排序时改用 Order + Limit(1)
  3. 错误兜底:结合 FindLimit 避免阻断业务流程。
  4. 索引优化:高频查询场景为时间字段添加索引。

总结

Last 是 GORM 中基于主键降序的高效查询方法,适用于有序数据检索。理解其主键依赖性和索引机制,可避免性能陷阱(如全表扫描),同时通过合理设计模型和查询条件提升效率。

CURD

创建

一、基本用法:插入单条记录

  1. 结构体指针传递
    使用 Create 方法时需传递结构体的指针,以便 GORM 能够回填数据库生成的值(如自增主键 ID、时间戳字段)到原结构体中。

    user := User{Name: "张三", Age: 25}
    result := db.Create(&user)  // 传递指针
    fmt.Println("插入的主键:", user.ID)  // 自动回填自增 ID
    
  2. 错误与影响行数
    Create 返回的 result 对象包含操作状态:

    if result.Error != nil {// 处理错误(如唯一约束冲突)
    }
    fmt.Println("影响行数:", result.RowsAffected)
    

二、字段控制:选择性插入

  1. Select 指定字段
    仅插入指定字段,其他字段依赖数据库默认值或允许为 NULL:

    db.Select("Name", "Age").Create(&user)
    // 仅插入 Name 和 Age 字段
    
  2. Omit 忽略字段
    排除特定字段的插入:

    db.Omit("CreatedAt", "Email").Create(&user)
    // 排除 CreatedAt 和 Email 字段
    

三、批量插入

  1. 切片批量插入
    通过传递结构体切片实现批量插入,GORM 会自动生成一条 SQL 语句,提高效率:

    users := []User{{Name: "李四", Age: 30},{Name: "王五", Age: 28},
    }
    db.Create(&users)
    // 批量插入后,users 中每个元素的 ID 会被回填
    
  2. 分批次插入(CreateInBatches
    大数据量时按批次插入,减少内存占用:

    db.CreateInBatches(&users, 100)  // 每批插入 100 条
    

四、高级用法

GORM 对 map 类型的限制
使用 map 插入数据时,GORM 不会自动展开嵌套结构体的字段,仅支持基础类型(intstringtime.Time)或实现了 driver.Valuer 接口的自定义类型。

  1. 使用 Map 插入数据
    直接通过 map 结构插入记录,但需注意此时不会触发模型关联(如钩子函数):

    db.Model(&User{}).Create(map[string]interface{}{"Name": "赵六", "Age": 22,
    })
    
  2. 冲突处理
    通过 Clauses 处理唯一约束冲突,例如忽略冲突继续插入:

    db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)
    

五、事务中的使用

在事务内部使用 Create 确保原子性:

db.Transaction(func(tx *gorm.DB) error {if err := tx.Create(&user).Error; err != nil {return err  // 回滚事务}// 其他操作...return nil  // 提交事务
})

六、注意事项

  1. 默认值与 NULL
    未显式赋值的字段:
    • 若数据库字段有默认值,自动填充;
    • 若字段允许 NULL,插入 NULL;
    • 若字段不允许 NULL 且无默认值,会触发错误。
  2. 钩子函数
    Create 操作会触发模型的 BeforeCreateAfterCreate 钩子函数,可用于数据校验或日志记录。
  3. 性能优化
    批量插入时建议使用 CreateInBatches,避免单条 SQL 语句过长。

通过以上方法,Create 可以灵活应对不同场景的插入需求,同时保证数据的一致性和操作的高效性。

更新

1. Save 方法

功能:根据主键是否存在决定执行插入或更新操作。若记录存在(主键非零),则更新所有字段(包括零值);若不存在,则插入新记录。
示例

user := User{ID: 1, Name: "Alice", Age: 30}
db.Save(&user)  // 存在 ID=1 则更新,否则插入

特点

  • 即使字段为零值(如 Age: 0),也会被更新。
  • 若主键存在则执行 UPDATE,否则执行INSERT

适用场景

  • 需要覆盖所有字段的插入或更新操作,尤其是零值需持久化的场景。

2. Update 方法

功能:更新单个字段,需明确指定条件,否则可能触发全局更新错误(ErrMissingWhereClause)。
示例

// 通过 Model 指定主键条件
db.Model(&user).Update("name", "hello")
// SQL: UPDATE users SET name='hello' WHERE id=111;// 自定义条件
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")

特点

  • 单字段操作:仅更新指定列,其他字段不受影响。
  • 条件必需:必须通过WhereModel指定条件,避免误更新全部记录。
  • 零值处理:即使字段为零值也会更新(如Update("age", 0))。

适用场景

  • 仅需修改单个字段且条件明确的情况。

3. Updates 方法

功能:更新多个字段,支持 structmap 参数,默认忽略零值(使用 struct 时)。
示例

// 使用 struct(忽略零值)
db.Model(&user).Updates(User{Name: "Charlie", Age: 0})  // 仅更新 Name// 使用 map(包含零值)
db.Model(&user).Updates(map[string]interface{}{"Name": "Charlie", "Age": 0})

特点

  • 动态过滤字段:struct参数自动忽略零值字段,map参数更新所有字段。
  • 条件控制:支持链式调用SelectOmit指定/排除字段。
  • 批量更新:结合Where可批量更新符合条件的记录。

适用场景

  • 多字段更新且需灵活控制零值处理的场景(如部分字段可选更新)。

4. UpdateColumn 方法

功能:跳过钩子函数(如 BeforeSaveBeforeUpdate)直接更新单个字段,需显式指定条件。
示例

db.Model(&user).UpdateColumn("name", "David")

特点

  • 绕过钩子:不触发模型的生命周期钩子,提升性能但可能绕过业务逻辑。
  • 原子操作:仅执行单字段更新,适用于需要高效、直接的场景。

适用场景

  • 需要跳过钩子或进行高效单字段更新的场景(如后台批量任务)。

关键区别总结

方法适用场景零值处理钩子触发批量支持条件必需性
Save全字段覆盖或插入包含零值主键判断
Update单字段更新包含零值
Updates多字段动态更新可选
UpdateColumn跳过钩子的单/多字段更新包含零值

注意事项

  1. 并发问题:Save在高并发下可能因版本问题导致主键冲突,建议使用稳定版本。
  2. 条件安全:UpdateUpdates必须指定条件,否则可能误更新全表。
  3. 零值策略:通过mapSelect强制更新零值,避免数据不一致。
  4. 性能优化:批量操作时,优先使用UpdateColumnUpdates减少钩子开销。

通过合理选择方法,可显著提升数据库操作的效率和安全性。

删除

一、基础删除操作

1. 根据主键删除单条记录

需确保删除对象包含主键值,否则可能触发批量删除:

// 示例:删除 ID=10 的 User 记录 
db.Delete(&User{}, 10)  
// SQL: DELETE FROM users WHERE id = 10;
2. 附加条件删除

通过 Where 方法添加额外条件,增强删除的精确性:

db.Where("name = ?", "jinzhu").Delete(&User{ID: 10})  
// SQL: DELETE FROM users WHERE id = 10 AND name = "jinzhu"; 
3. 批量删除

通过主键列表或条件筛选批量删除记录:

// 删除主键为 1,2,3 的记录 
db.Delete(&User{}, []int{1, 2, 3})  
// SQL: DELETE FROM users WHERE id IN (1,2,3);// 通过条件批量删除 [3](@ref)
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})  
// SQL: DELETE FROM emails WHERE email LIKE "%jinzhu%";

二、安全机制与错误处理

1. 防止全局删除

GORM 默认禁止无条件的批量删除,需显式设置:

// 错误示例:触发 ErrMissingWhereClause 
db.Delete(&User{}).Error  // gorm.ErrMissingWhereClause// 解决方案:添加条件或启用 AllowGlobalUpdate
db.Where("1 = 1").Delete(&User{})  // 显式条件  
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})  // 全局模式 
2. 软删除(逻辑删除)

若模型包含 gorm.DeletedAt 字段,删除操作将自动标记为软删除:

type User struct {gorm.Model  // 包含 DeletedAt 字段Name string
}db.Delete(&user)  
// SQL: UPDATE users SET deleted_at = NOW() WHERE id = 10; 
3. 硬删除(物理删除)

通过 Unscoped 绕过软删除逻辑:

db.Unscoped().Delete(&user)  
// SQL: DELETE FROM users WHERE id = 10; 

三、高级功能

1. 钩子函数(Hooks)

在删除前/后插入自定义逻辑(如权限校验):

func (u *User) BeforeDelete(tx *gorm.DB) error {if u.Role == "admin" {return errors.New("admin 用户禁止删除")  // 阻止管理员被删除 }return nil
}
2. 返回被删除的数据

支持通过 RETURNING 子句获取被删除的数据(需数据库支持):

var users []User
db.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)  
// 返回所有被删除的 admin 用户信息 

四、注意事项

  1. 主键类型兼容性
    主键值可以是数字或字符串,GORM 自动转换类型 :

    db.Delete(&User{}, "10")  // 字符串主键
    
  2. 性能优化

    • 批量删除时优先使用主键列表([]int{1,2,3})而非循环单条删除;
    • 避免在事务外执行高频删除操作。
  3. 零值处理
    Delete 方法不会忽略字段的零值,需通过条件显式过滤 。


总结对比表

操作类型特点适用场景
主键删除精确删除单条记录已知主键的删除需求
条件批量删除通过 WHERE 筛选多记录按业务规则批量清理数据
软删除标记删除而非物理删除数据恢复需求或审计要求
钩子拦截自定义删除前校验逻辑权限控制或数据保护

合理选择删除策略,可兼顾数据安全性与操作效率。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词