Golang GORM ORM 基础

Golang GORM ORM 基础 GORM ORM 基础核心概念GORM 是 Go 生态中最流行的 ORM 框架。它的设计哲学是用 Go 结构体描述数据库表用方法链表达查询逻辑让开发者用面向对象的方式操作数据库同时保留逃生通道Raw SQL。ORM vs Raw SQL// Raw SQLrows,_:db.Query(SELECT id, name, age FROM users WHERE age ? AND name LIKE ?,18,张%)// GORMvarusers[]User db.Where(age ? AND name LIKE ?,18,张%).Find(users)ORM 的价值在于结构体即表结构减少手写 SQL 的拼写错误、自动类型转换、关联查询自动化、迁移工具。代价是学习曲线和一定的性能损耗。连接与初始化import(gorm.io/gormgorm.io/driver/sqlite// 或 mysql, postgres)db,err:gorm.Open(sqlite.Open(test.db),gorm.Config{})GORM 的驱动层与database/sql不同它自己封装了连接管理但底层仍然可以接入*sql.DB。模型定义GORM 约定结构体名蛇形化为表名字段名蛇形化为列名。typeUserstruct{IDuintgorm:primaryKey// 主键Namestringgorm:size:100;not null// VARCHAR(100), NOT NULLEmailstringgorm:uniqueIndex// 唯一索引Ageintgorm:default:0// 默认值 0Birthday*time.Time// 指针类型 → 可 NULLCreatedAt time.Time// GORM 自动管理创建时间UpdatedAt time.Time// GORM 自动管理更新时间DeletedAt gorm.DeletedAtgorm:index// 软删除}约定优于配置Go 字段数据库列说明IDid名为 ID 的字段自动作为主键CreatedAtcreated_at记录创建时自动写入UpdatedAtupdated_at每次更新自动刷新DeletedAtdeleted_at软删除标记查询时自动过滤标签语法typeProductstruct{gorm.Model// 内嵌 ID CreatedAt UpdatedAt DeletedAtCodestringgorm:column:product_code;type:varchar(50);uniqueIndex:idx_codePriceuintgorm:check:price 0// CHECK 约束}常用标签column列名、type列类型、size长度、primaryKey、uniqueIndex、index、not null、default、comment、check。CRUD 操作Createuser:User{Name:张三,Email:zstest.com,Age:28}db.Create(user)// 插入并回填自增 ID// 批量插入db.Create([]User{{Name:李四,Email:lstest.com,Age:32},{Name:王五,Email:wwtest.com,Age:25},})// 选择性插入只写入指定字段db.Select(Name,Email).Create(User{Name:赵六,Email:zltest.com,Age:0})Read// 单条查询varuser User db.First(user,1)// 按主键db.First(user,email ?,zstest.com)// 按条件// 多条查询varusers[]User db.Find(users)// 全部db.Where(age ?,25).Find(users)// 条件db.Where(name LIKE ?,张%).Find(users)// 排序 分页db.Order(age DESC).Limit(10).Offset(0).Find(users)// 计数varcountint64db.Model(User{}).Where(age ?,20).Count(count)Update// 更新单个字段db.Model(user).Update(age,29)// 更新多个字段struct 方式零值字段会被忽略db.Model(user).Updates(User{Name:张三丰,Age:30})// 更新多个字段map 方式零值也会更新db.Model(user).Updates(map[string]interface{}{age:0,name:张三})// 批量更新db.Model(User{}).Where(age ?,25).Update(age,25)Delete// 软删除有 DeletedAt 字段时自动启用 db.Delete(user) // 设置 deleted_at now() db.Unscoped().Delete(user) // 真正删除 // 查询包含已软删除的记录 db.Unscoped().Find(users) // 批量删除 db.Where(age ?, 18).Delete(User{})链式调用与方法链GORM 的查询 API 是链式构建器模式方法分三类类型方法说明链式方法Where,Order,Limit,Select,Joins构建查询条件返回*gorm.DBFinisher 方法Find,First,Create,Update,Delete,Count触发执行新会话方法Session,WithContext,Scopes创建新会话上下文// 链式 Finisherdb.Where(age ?,20).Order(created_at DESC).Limit(20).Offset(0).Find(users)完整练习代码// gorm_basics.gopackagemainimport(fmtlogtimegorm.io/driver/sqlitegorm.io/gormgorm.io/gorm/logger)// User 用户模型typeUserstruct{IDuintgorm:primaryKeyNamestringgorm:size:100;not nullEmailstringgorm:size:200;uniqueIndexAgeintgorm:default:0Birthday*time.Time CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAtgorm:indexProducts[]Productgorm:foreignKey:UserID}// Product 产品模型演示外键关联typeProductstruct{IDuintgorm:primaryKeyNamestringPricefloat64Stockint// 一对多一个用户有多个产品UserIDuint// 外键User Usergorm:foreignKey:UserID// 关联}funcmain(){// 1. 连接数据库db,err:gorm.Open(sqlite.Open(:memory:),gorm.Config{Logger:logger.Default.LogMode(logger.Info),})iferr!nil{log.Fatal(err)}// 2. 自动迁移建表iferr:db.AutoMigrate(User{},Product{});err!nil{log.Fatal(err)}fmt.Println( 自动迁移完成 )// // 3. Create// fmt.Println(\n Create )// 单条插入birthday:time.Date(1998,5,15,0,0,0,0,time.UTC)user:User{Name:张三,Email:zstest.com,Age:28,Birthday:birthday}result:db.Create(user)fmt.Printf( 插入: ID%d, 影响行数%d\n,user.ID,result.RowsAffected)// 批量插入users:[]User{{Name:李四,Email:lstest.com,Age:32},{Name:王五,Email:wwtest.com,Age:25},{Name:赵六,Email:zltest.com,Age:35},{Name:孙七,Email:sqtest.com,Age:22},}db.Create(users)fmt.Printf( 批量插入: %d 条\n,len(users))// // 4. Read// fmt.Println(\n Read )// 按主键查询varu User db.First(u,1)fmt.Printf( First(1): %s, %d岁\n,u.Name,u.Age)// 条件查询varyoungUsers[]User db.Where(age ?,30).Order(age ASC).Find(youngUsers)fmt.Printf( age30 的用户 (%d人):\n,len(youngUsers))for_,yu:rangeyoungUsers{fmt.Printf( %s, %d岁\n,yu.Name,yu.Age)}// Select 指定字段varnames[]struct{NamestringAgeint}db.Model(User{}).Select(name,age).Where(age ?,28).Scan(names)fmt.Printf( age28 (SelectScan): )for_,n:rangenames{fmt.Printf(%s(%d) ,n.Name,n.Age)}fmt.Println()// 分页查询varpageUsers[]Uservartotalint64db.Model(User{}).Count(total)db.Limit(2).Offset(0).Order(id).Find(pageUsers)fmt.Printf( 分页(page1, size2): 共%d条, 本页%d条\n,total,len(pageUsers))// // 5. Update// fmt.Println(\n Update )// struct 更新零值字段被忽略db.Model(u).Updates(User{Name:张三丰,Age:30})fmt.Printf( Updates(struct): %s, %d岁\n,u.Name,u.Age)// map 更新零值也会更新db.Model(u).Updates(map[string]interface{}{age:0})db.First(u,u.ID)fmt.Printf( Updates(map): %s, %d岁 (age0 生效)\n,u.Name,u.Age)// 批量更新db.Model(User{}).Where(age ?,25).Update(age,25)varupdated[]User db.Where(age ?,25).Find(updated)fmt.Printf( 批量更新 age25 → age25: %d人\n,len(updated))// // 6. Delete软删除// fmt.Println(\n Delete软删除 )varzhao User db.Where(name ?,赵六).First(zhao)db.Delete(zhao)// 普通查询看不到已删除的varcountint64db.Model(User{}).Count(count)fmt.Printf( 删除赵六后剩余用户数: %d\n,count)// Unscoped 可以查到软删除的记录varallUsers[]User db.Unscoped().Find(allUsers)fmt.Printf( Unscoped 查询含软删除: %d条\n,len(allUsers))// // 7. 关联关系一对多// fmt.Println(\n 关联关系 )// 为张三创建产品db.Create(Product{Name:Go编程书,Price:89.5,Stock:100,UserID:1})db.Create(Product{Name:机械键盘,Price:459.0,Stock:50,UserID:1})db.Create(Product{Name:鼠标,Price:99.0,Stock:200,UserID:2})// 预加载查询避免 N1 问题varuserWithProducts User db.Preload(Products).First(userWithProducts,1)fmt.Printf( %s 的产品 (%d件):\n,userWithProducts.Name,len(userWithProducts.Products))for_,p:rangeuserWithProducts.Products{fmt.Printf( %s: %.2f (库存%d)\n,p.Name,p.Price,p.Stock)}// // 8. 钩子Hooks// fmt.Println(\n Hooks 演示 )// BeforeCreate 钩子会在插入前自动调用newUser:User{Name:测试钩子,Email:hooktest.com,Age:-1}db.Create(newUser)fmt.Printf( 创建前 Age-1, 创建后: %d (钩子修正了负数)\n,newUser.Age)}// BeforeCreate GORM 钩子插入前自动调用func(u*User)BeforeCreate(tx*gorm.DB)(errerror){// 年龄不能为负数自动修正为 0ifu.Age0{u.Age0}returnnil}运行方式mkdir-pdemo4cddemo4 go mod init demo4 go get gorm.io/gorm gorm.io/driver/sqlite go run main.go要点总结GORM 用结构体描述表标签控制列属性gorm.Model内嵌了 ID 时间戳 软删除四件套链式方法构建条件Finisher 方法触发执行链式方法返回*gorm.DB可继续链式调用struct Updates 零值被忽略需要更新零值时用map[string]interface{}软删除通过DeletedAt实现查询自动过滤Unscoped()可查已删除记录Preload 解决 N1 问题一条 SQL JOIN 加载关联数据避免循环查询Hooks 在生命周期点自动触发BeforeCreate/AfterCreate/BeforeUpdate/BeforeDelete等AutoMigrate 只增不减新增字段和表会自动创建但删除字段需要手动操作或用迁移工具