GORM 常见操作从入门到能写 DAO
个人主页会编程的土豆欢迎来访作者简介后端学习者❄️个人专栏数据结构与算法数据库leetcode✨那些你一个人走过的夜路终将化作照亮未来的光适合对象会一点 Go 和 SQL刚开始用 GORM 的同学结合项目影院票务Gin GORM MySQL读完目标能看懂dao/里大部分代码并能自己写出简单增删改查写在前面很多人第一次碰 GORM会有两个错觉「这不就是把 SQL 换成英文链式调用吗」——对入门可以这么理解「会了 Create/Find 就算会 GORM 了」——不够课设/工作里还常碰到条件拼接、Omit、联表、Scan、事务、FOR UPDATE本文不追求官方文档全文搬运而是按票务项目真实写法把最常用的操作讲透一、GORM 是什么在项目里处于哪一层GORM Go 的 ORM 框架用结构体和链式 API 操作数据库少手写拼接 SQL在本项目中的位置Handlerhandlers/ ↓ 调用 DAOdao/ ←── 几乎所有 GORM 写在这里 ↓ 使用 db.DB*gorm.DB ↓ MySQL记住一条规矩尽量不要在 Handler 里直接写db.DB.xxxGORM 操作收敛到 dao二、连接数据库项目怎么初始化 GORM对应文件db/db.govar DB *gorm.DB func Init(dsn string) error { var err error DB, err gorm.Open(mysql.Open(dsn), gorm.Config{ Logger: logger.Default.LogMode(logger.Warn), }) if err ! nil { return err } sqlDB, _ : DB.DB() sqlDB.SetMaxOpenConns(20) sqlDB.SetMaxIdleConns(5) sqlDB.SetConnMaxLifetime(30 * time.Minute) return sqlDB.Ping() }要点点说明gorm.Open打开连接得到全局*gorm.DBmysql.Open(dsn)用 MySQL 驱动dsn 一般来自配置DB.DB()拿到底层*sql.DB用来配连接池全局变量db.DBdao 里到处db.DB.Create(...)新手注意本项目表结构主要靠sql/schema.sql建好不是一上来就全靠AutoMigrate生成课设这样更可控三、模型Model结构和表怎么对应GORM 靠struct tag知道字段怎么映射示例简化自models.Usertype User struct { ID int64 gorm:primaryKey;autoIncrement json:id Username string gorm:size:50;uniqueIndex;not null json:username Password string gorm:size:32;not null json:- Nickname string gorm:size:50;not null;default: json:nickname Role int gorm:not null;default:0 json:role CreatedAt time.Time json:created_at } func (User) TableName() string { return users }3.1 常见 gorm tagtag含义例子primaryKey主键idautoIncrement自增size:50字符串长度varchar(50)语义not null非空uniqueIndex唯一索引用户名index普通索引column:rows_num字段名和列名不同影厅行数type:text指定列类型简介-只读查出来可以填写入时忽略GroupName、MovieTitlejson:-JSON 不输出不是 gorm 专属密码3.2TableName()干嘛告诉 GORM这个结构体对应哪张表不写的话GORM 常会按复数规则猜表名课设里显式写更稳3.3 为什么有「假字段」比如Movie.GroupName、Order.MovieTitle表里不一定有这一列联表查询时SELECT ... AS group_name扫进结构体用gorm:-Omit(GroupName)避免Create时乱写不存在的列这是本项目非常实用的技巧。四、CRUD 总览先建立地图操作SQL 直觉GORM 常见写法增 CreateINSERTdb.Create(obj)查 ReadSELECTFirst/Find/Scan改 UpdateUPDATEUpdate/Updates删 DeleteDELETEDelete下面按「增 → 查 → 改 → 删 → 条件 → 联表 → 事务」展开。五、增Create插入一条 / 批量插入5.1 插入一条// dao/user.go func CreateUser(u *models.User) error { return db.DB.Create(u).Error }等价思路INSERT INTO users (...) VALUES (...);成功后若主键自增u.ID通常会被 GORM 回填。5.2 为什么经常看到Omit// 创建电影时忽略联表读出来的 GroupName return db.DB.Omit(GroupName).Create(m).Error // 创建订单时忽略展示用字段 return tx.Omit(MovieTitle, HallName, StartTime).Create(order).ErrorOmit 插入/更新时跳过这些字段。联表展示字段、统计字段、不想被客户端篡改的字段都适合 Omit。5.3 批量插入生成场次座位时return db.DB.CreateInBatches(seats, 100).Error意思切片里很多Seat每 100 条一批插入减轻单次 SQL 过大。六、查ReadFirst / Find / 主键查询 / 找不到怎么办6.1 按主键查一条First(obj, id)err : db.DB.First(h, id).Error大致等价SELECT * FROM halls WHERE id ? LIMIT 1;6.2 带条件查一条Where(...).Firsterr : db.DB.Where(username ?, username).First(u).Error永远用?占位符传参数不要自己拼字符串防 SQL 注入。6.3 查列表Findvar list []models.MovieGroup err : db.DB.Order(id).Find(list).ErrorSELECT * FROM movie_groups ORDER BY id;6.4 记录不存在怎么判断err : db.DB.Where(username ?, username).First(u).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil // 本项目约定没找到返回 (nil, nil) }新手坑err ! nil不一定是「系统挂了」也可能是「没这条数据」业务上要分清ErrRecordNotFoundvs 真正的数据库错误6.5FirstvsFindvsScan很重要API典型用途说明First取一条按模型表查找不到 →ErrRecordNotFoundFind取多条按模型表查找不到常常是空切片不一定当错误Scan把查询结果扫进结构体联表自定义 Select 时最常用本项目电影列表是联表所以多用Scanerr : q.Order(m.id DESC).Scan(list).Error七、改Update改一个字段 / 改多个字段7.1 改单个字段Update// 取消场次只改 status return db.DB.Model(models.Schedule{}). Where(id ?, id). Update(status, models.ScheduleCancelled).Error7.2 改多个字段Updatesreturn db.DB.Model(g).Updates(map[string]interface{}{ name: g.Name, description: g.Description, }).Error锁座时也是Updatestx.Model(models.Seat{}).Where(id ?, sid).Updates(map[string]interface{}{ status: models.SeatLocked, lock_user_id: userID, lock_until: lockUntil, order_id: order.ID, })7.3Update/Updates使用建议写法适合Update(col, val)只改一列Updates(map[string]interface{}{...})改多列零值也能更新如把某字段更新成 0Updates(结构体)也行但注意结构体零值字段可能被跳过课设里多字段更新用 map 往往更不容易踩零值坑7.4 一定要有ModelWhere吗更新时强烈建议db.Model(models.Seat{}).Where(id ?, sid).Updates(...)避免误更新范围过大。没有条件的更新非常危险八、删Delete8.1 按主键删return db.DB.Delete(models.Movie{}, id).Error大致DELETE FROM movies WHERE id ?;8.2 按条件删res : db.DB.Where(end_time ?, before).Delete(models.Schedule{}) return res.RowsAffected, res.ErrorRowsAffected 影响了多少行cleaner 打日志会用到。8.3 软删除GORM 支持DeletedAt软删除。本票务项目座位/订单等主要是改状态不是软删别和Delete物理删除混为一谈。九、条件构造Where / Order / Limit / Count9.1 动态拼接条件列表筛选神器电影列表q : movieQuery() if onlyOn { q q.Where(m.status ?, models.MovieOn) } if keyword ! { kw : % keyword % q q.Where((m.title LIKE ? OR m.director LIKE ? OR m.actors LIKE ?), kw, kw, kw) } if groupID 0 { q q.Where(m.group_id ?, groupID) } err : q.Order(m.id DESC).Scan(list).Error这就是「有参数才加条件」——对应首页?q、?group1。9.2 Order / Limitq.Order(m.rating_avg DESC, m.rating_count DESC).Limit(limit).Scan(list)推荐列表按评分排序只取前 N 条。9.3 Countvar n int64 err : db.DB.Model(models.Seat{}). Where(schedule_id ?, scheduleID). Count(n).Error用来统计某场次座位数量等。9.4 Pluck只取一列超时订单清理var ids []int64 tx.Model(models.Order{}). Where(status ? AND pay_deadline NOW(), models.OrderPending). Pluck(id, ids)只抽出id列表再逐个处理。十、联表查询Table Select Joins Scan简单Find只适合「单表且字段都在模型里」。订单要带电影名、影厅名就要联表。本项目典型写法func orderQuery() *gorm.DB { return db.DB.Table(orders AS o). Select(o.id,o.order_no,o.user_id,o.schedule_id, IFNULL(m.title,) AS movie_title, IFNULL(h.name,) AS hall_name, IFNULL(s.start_time,NOW()) AS start_time, o.seat_info,o.amount,o.status,o.pay_deadline,o.created_at,o.paid_at). Joins(LEFT JOIN schedules s ON o.schedule_ids.id). Joins(LEFT JOIN movies m ON s.movie_idm.id). Joins(LEFT JOIN halls h ON s.hall_idh.id) } func GetOrder(id int64) (*models.Order, error) { var o models.Order err : orderQuery().Where(o.id ?, id).Scan(o).Error // ... }怎么读Table(orders AS o)主表起别名Select(...)要哪些列联表字段用AS movie_title对上结构体Joins(...)LEFT JOIN/JOINScan(o)扫进结构体电影联分组同理moviesLEFT JOINmovie_groups。这比硬背 Association / Preload 更贴近很多课设的 SQL 思维也好排查。十一、链式调用怎么理解GORM 很多方法返回的还是*gorm.DB所以可以一路点下去db.DB. Where(schedule_id ?, scheduleID). Order(row_no, col_no). Find(list)可以想成在拼 SQL最后Find/First/Scan/Create...才真正执行。注意q : db.DB.Where(status ?, 1) q2 : q.Where(id ?, 10) // 一般是在原条件下继续加列表筛选时把q重新赋回去q q.Where(...)是本项目的常见写法。十二、事务 Transaction多步要么全成要么全败下单要「建订单 锁多个座位」不能订单建了座位没锁成功还留下半成品。err : db.DB.Transaction(func(tx *gorm.DB) error { // 注意事务里用 tx不要用 db.DB // 1) 锁行并检查座位 // 2) Create order // 3) Updates seats return nil // 返回 nil → 提交返回 error → 回滚 })规则规则说明回调返回nil提交事务回调返回error回滚里面用tx所有操作挂在同一事务上支付、取消订单、释放超时订单本项目同样包在Transaction里十三、行锁 FOR UPDATE防超卖的关键写法import gorm.io/gorm/clause err : tx.Clauses(clause.Locking{Strength: UPDATE}). Where(id ? AND schedule_id ?, sid, scheduleID). First(seat).Error生成的 SQL 直觉SELECT * FROM seats WHERE id? AND schedule_id? FOR UPDATE;含义在事务中锁定这一行别人想改同一行会等待避免两人同时读到「空闲」再一起下单。配合业务字段status锁定、lock_user_id、lock_until才完整行锁管并发瞬间业务锁字段管支付窗口期内占座十四、Upsert / 冲突更新评分场景同一用户对同一电影重复评分希望「有则更新无则插入」tx.Clauses(clause.OnConflict{ // 按唯一键冲突时更新某些列 }).Create(r)具体 Columns 以你们dao/comment.go为准。这比先查再决定 insert/update在并发下更干净。十五、错误处理固定套路建议背下来err : db.DB.Where(username ?, name).First(u).Error if err ! nil { if errors.Is(err, gorm.ErrRecordNotFound) { // 业务用户不存在 return nil, nil } // 系统数据库真出错 return nil, err } return u, nil以及执行类if err : db.DB.Create(u).Error; err ! nil { return err }几乎所有 dao 函数都是(结果, error)或error十六、一张「看见代码就知道在干嘛」速查表你想做的事优先写法插入Create/CreateInBatches主键取一条First(m, id)条件取一条Where.First条件取多条Where.Find联表自定义列TableSelectJoinsScan改一列Model.Where.Update改多列Model.Where.Updates(map)删除Delete/Where.Delete统计Count抽一列Pluck跳过字段Omit多步骤原子Transaction行锁Clauses(Locking{UPDATE})排序/条数Order/Limit

相关新闻

2026年电脑录音转文字APP测评好技术让转写清楚整理快更省心

2026年电脑录音转文字APP测评好技术让转写清楚整理快更省心

这次做的2026年电脑端录音转文字APP横向测评,结果显示听脑AI更适配职场新人处理入职培训记录、产品知识学习类的转写整理需求,核心原因是它能完成从转写到知识巩固的完整闭环,1小时录音可2分钟内出稿。它仅适配个人日常转写学习使用&#xff…

2026/10/11 3:12:06 阅读更多 →
PlayIntegrityFix配置文件详解:target.txt与keybox.xml最佳实践

PlayIntegrityFix配置文件详解:target.txt与keybox.xml最佳实践

PlayIntegrityFix配置文件详解:target.txt与keybox.xml最佳实践 【免费下载链接】PlayIntegrityFix Google H*ck is a module designed to bypass play integrity checks, especially for China Roms, and offers advanced configuration and development capabilit…

2026/10/11 12:48:03 阅读更多 →
WeTextProcessing终极教程:让你的文本处理效率提升10倍

WeTextProcessing终极教程:让你的文本处理效率提升10倍

WeTextProcessing终极教程:让你的文本处理效率提升10倍 【免费下载链接】WeTextProcessing Text Normalization & Inverse Text Normalization 项目地址: https://gitcode.com/gh_mirrors/we/WeTextProcessing WeTextProcessing是一款专注于文本规范化&a…

2026/10/2 10:41:16 阅读更多 →

最新新闻

8G显存本地部署MiniMax H3:视频生成与ComfyUI接入全攻略

8G显存本地部署MiniMax H3:视频生成与ComfyUI接入全攻略

第一次在8G显存的卡上把MiniMax H3本地版跑起来的时候,我自己都有点没缓过劲。最初印象里,视频生成模型想本地部署怎么也得16G显存起步,结果在量化方案的加持下不仅能跑,还能连续产出多条可用的漫剧分镜。这篇就把我从环境搭建、模…

2026/10/12 8:27:02 阅读更多 →
iff链:用链式等价证明破解循环论证的双向陷阱

iff链:用链式等价证明破解循环论证的双向陷阱

我最早对“当且仅当”四个字产生警惕,是在一次代码评审里。当时某个公开算法库的文档宣称“某数据结构能在常数时间内判定可达性当且仅当存在一条长度不超过给定阈值的路径”,实现者也把证明贴成了两段:“⇒”是正向,“⇐”是反向…

2026/10/12 8:27:02 阅读更多 →
Kaggle房价预测高维回归:从特征工程到Stacking融合实战

Kaggle房价预测高维回归:从特征工程到Stacking融合实战

简介:这份PDF资料面向具备一定Python与机器学习基础、希望系统掌握高维数据分类与回归实战的读者,以Kaggle经典房价预测赛题为载体,重点讲解如何用Stacking思维融合多种模型的优势。内容围绕完整流程展开:从pandas读取train与test…

2026/10/12 8:27:02 阅读更多 →
OpenCV傅里叶变换实战:频域滤波从原理到工业缺陷检测

OpenCV傅里叶变换实战:频域滤波从原理到工业缺陷检测

1. 为什么图像处理绕不开傅里叶变换刚接触OpenCV那会儿,我对傅里叶变换的态度就是两个字:跳过。理由也很简单——灰度、二值化、边缘检测、形态学操作,这些看得见摸得着的东西已经够用了,何必去碰那个满是复数、频域、频谱图的数学…

2026/10/12 8:27:02 阅读更多 →
Laravel vs ThinkPHP:架构基因与性能成本的选型权衡

Laravel vs ThinkPHP:架构基因与性能成本的选型权衡

前几天有个朋友问我:公司要起一个新项目,技术选型投票时团队吵翻了,一半人坚持用Laravel,理由是“现代架构、生态好、写起来优雅”,另一半人守着ThinkPHP不放,理由是“启动快、性能好、国内资料多”。我听完…

2026/10/12 8:27:01 阅读更多 →
不会编程,用AI写代码靠谱吗?TaoToken统一Key实测Cline MCP与Windsurf BYOK

不会编程,用AI写代码靠谱吗?TaoToken统一Key实测Cline MCP与Windsurf BYOK

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 8:26:01 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →