ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

GORM入门:ORM映射与CRUD

2026/8/16 15:45:54 拓冰建站 浏览量
GORM入门:ORM映射与CRUD GORM入门:ORM映射与CRUD摘要: 本篇从GORM的安装和连接讲起演示模型定义与表结构映射、AutoMigrate自动建表、完整的CRUD操作和条件查询技巧讲解软删除机制和Unscoped用法分享软删除导致唯一索引冲突的踩坑经历对比database/sql、sqlx和GORM三种数据访问方式的开发效率。开篇故事上一篇我们用database/sql写了一套CRUD每个查询都要手动Scan字段加一个字段就得改SQL和Scan两处。我有个项目用户表改了三次结构每次改完都要全局搜索所有涉及User的SQL和Scan生怕漏一个。后来我把这个项目迁移到GORM。定义好structGORM自动处理字段映射加字段只需要改struct和调一次AutoMigrate。开发效率提升非常明显原来一个接口要写30分钟SQL和绑定代码迁移后10分钟搞定。当然GORM也有它的坑。这篇我们从入门开始把CRUD和模型映射讲透坑留到后面说。一、安装与连接packagemainimport(logtimegorm.io/driver/mysqlgorm.io/gorm)funcmain(){// DSN与database/sql的格式一致dsn:root:123456tcp(127.0.0.1:3306)/testdb?charsetutf8mb4parseTimetruelocLocal// gorm.Open建立连接并初始化// 第二个参数是配置项后面性能篇会详细讲db,err:gorm.Open(mysql.Open(dsn),gorm.Config{})iferr!nil{log.Fatal(连接数据库失败:,err)}// 获取底层sql.DB配置连接池// GORM底层仍然使用database/sqlsqlDB,_:db.DB()sqlDB.SetMaxOpenConns(25)// 最大连接数sqlDB.SetMaxIdleConns(10)// 最大空闲连接数sqlDB.SetConnMaxLifetime(5*time.Minute)// 连接最大存活时间log.Println(GORM连接成功)}GORM底层仍然使用database/sql所以连接池配置方式完全一样。gorm.Open返回的是*gorm.DB通过db.DB()可以拿到底层的*sql.DB。二、模型定义与自动建表GORM用struct tag来映射数据库表结构。import(timegorm.io/gorm)// User 用户模型// GORM约定struct名为User表名为userstypeUserstruct{// ID是GORM的默认主键// 类型为uint对应数据库BIGINT UNSIGNEDIDuintgorm:primaryKey// size控制VARCHAR长度not null非空约束Namestringgorm:size:50;not null// uniqueIndex创建唯一索引Emailstringgorm:size:100;uniqueIndex// default设置默认值Ageintgorm:default:18Statusstringgorm:size:20;default:active// CreatedAt和UpdatedAt自动管理CreatedAt time.Time UpdatedAt time.Time// DeletedAt软删除字段// 有值表示已删除查询时自动过滤DeletedAt gorm.DeletedAtgorm:index}AutoMigrate会根据struct创建或更新表结构。// AutoMigrate自动建表// 已存在的表会追加缺失的列和索引// 不会删除已有的列也不会修改列类型db.AutoMigrate(User{})// 生成的SQL大概是这样// CREATE TABLE users (// id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,// name VARCHAR(50) NOT NULL,// email VARCHAR(100),// age BIGINT DEFAULT 18,// ...// PRIMARY KEY (id)// )注意AutoMigrate是追加式的只加不删。如果你删掉struct里的某个字段数据库表里对应的列还在。生产环境不建议用AutoMigrate管理表结构应该用migration工具比如golang-migrate做版本化迁移。三、CRUD操作// Create 插入记录funcCreate(db*gorm.DB){user:User{Name:张三,Email:zhangsanexample.com,Age:25,}// Create插入一条记录result:db.Create(user)// result.Error 错误信息// result.RowsAffected 受影响行数// user.ID 自动回填插入后的自增IDfmt.Println(新用户ID:,user.ID)}// 批量插入funcBatchCreate(db*gorm.DB){users:[]User{{Name:李四,Email:lisiexample.com,Age:30},{Name:王五,Email:wangwuexample.com,Age:28},}// 传入切片批量插入db.Create(users)// 每个元素的ID都会自动回填}// Read 查询操作funcRead(db*gorm.DB){varuser User// First按主键查询没找到返回ErrRecordNotFounddb.First(user,1)// SQL: SELECT * FROM users WHERE id 1 LIMIT 1// 按条件查询db.First(user,email ?,zhangsanexample.com)// 查询多条varusers[]User db.Find(users,age ?,20)// SQL: SELECT * FROM users WHERE age 20// 链式条件查询db.Where(name LIKE ?,%张%).Where(age ?,20).Order(age DESC).Limit(10).Offset(0).Find(users)// SQL: SELECT * FROM users// WHERE name LIKE %张% AND age 20// ORDER BY age DESC LIMIT 10 OFFSET 0}// Update 更新操作funcUpdate(db*gorm.DB){varuser User db.First(user,1)// Save保存所有字段user.Age26db.Save(user)// SQL: UPDATE users SET ... WHERE id 1// Update更新单个字段db.Model(user).Update(name,张三丰)// SQL: UPDATE users SET name 张三丰 WHERE id 1// Updates更新多个字段// 用map可以更新零值db.Model(user).Updates(map[string]interface{}{name:张三,age:27,})// SQL: UPDATE users SET name 张三, age 27 WHERE id 1}// Delete 删除操作funcDelete(db*gorm.DB){// 软删除设置deleted_at字段的值// 查询时GORM自动过滤已软删除的记录db.Delete(User{},1)// SQL: UPDATE users SET deleted_at NOW() WHERE id 1// Unscoped查询包含软删除的记录varusers[]User db.Unscoped().Find(users)// SQL: SELECT * FROM users (不带deleted_at过滤)// Unscoped永久删除db.Unscoped().Delete(User{},1)// SQL: DELETE FROM users WHERE id 1}四、条件查询技巧// 常用查询方式汇总funcQueryExamples(db*gorm.DB){varusers[]User// IN查询db.Where(id IN ?,[]int{1,2,3}).Find(users)// BETWEEN范围查询db.Where(age BETWEEN ? AND ?,20,30).Find(users)// OR条件db.Where(name ?,张三).Or(name ?,李四).Find(users)// Select指定字段减少网络传输量db.Select(id,name).Find(users)// SQL: SELECT id, name FROM users// Count计数varcountint64db.Model(User{}).Where(age ?,20).Count(count)// 分组与聚合查询typeResultstruct{AgeintTotalint}varresults[]Result db.Model(User{}).Select(age, count(*) as total).Group(age).Find(results)}五、独家踩坑:软删除的意外行为GORM默认开启软删除这给了我一个很大的惊喜。// 踩坑场景:唯一索引与软删除冲突typeUserstruct{IDuintEmailstringgorm:uniqueIndex// 唯一索引NamestringDeletedAt gorm.DeletedAtgorm:index// 软删除}// 用户注销后重新注册funcReRegister(db*gorm.DB,emailstring)error{// 先删除旧账号软删除db.Where(email ?,email).Delete(User{})// deleted_at被设值但记录还在表里// 再创建新账号user:User{Email:email,Name:新用户}err:db.Create(user).Error// 报错: Duplicate entry xxx for key idx_email// 唯一索引不关心deleted_at旧记录还在returnerr}用户注销后想用同一个邮箱重新注册结果唯一索引报重复。软删除只是把deleted_at设了值记录还在表里唯一索引照样拦截。这个问题有好几种解法。可以把唯一索引改成联合索引(email, deleted_at)但deleted_at是NULL时联合索引的行为在不同数据库有差异。也可以在业务层处理注销时把email加个后缀比如deleted_123xxx.com。最干净的方式是注销时判断是否已有软删除记录有就恢复更新而不是新建。软删除在大多数场景很方便但涉及唯一约束时要特别小心。六、对比分析特性database/sqlsqlxGORM开发效率低中高SQL控制力完全手动完全手动自动生成结构体映射手动Scan自动Scan自动映射软删除无无内置迁移工具无无AutoMigrate学习成本低低中从database/sql到GORM开发效率提升很大代价是失去了对SQL的完全控制。简单CRUD用GORM很爽复杂查询GORM的链式API有时候比直接写SQL还难读。下篇我们讲GORM进阶关联关系和事务那里GORM的优势和劣势都会更明显。总结GORM把重复的数据库操作代码抽象掉了模型定义即表结构CRUD一行搞定。记住两个要点struct tag控制映射规则软删除影响唯一索引。下一篇进入GORM进阶讲关联关系、钩子和事务。