在前面的文章中,我们的图书数据都存储在内存中——服务重启后数据全部丢失。在实际项目中,数据需要持久化存储,最常用的关系型数据库是 MySQL。GORM 是 Go 生态中最流行的 ORM 库,它提供了对象-关系映射能力,让我们可以用 Go 结构体和方法来操作数据库,而不需要手写 SQL。本文从 GORM 的安装与连接讲起,涵盖模型定义、自动迁移、CRUD 操作等核心功能,并通过一个完整的用户管理实战,帮你掌握 Go 语言操作数据库的基本技能。
一、ORM 是什么?为什么选择 GORM?
ORM(Object-Relational Mapping,对象关系映射) 是一种编程技术,用于在关系型数据库和面向对象编程语言之间建立映射关系。它将数据库表映射为编程语言中的对象/结构体,将 SQL 操作转换为方法调用。
使用 ORM 的主要好处:
开发效率高:用结构体和方法替代手写 SQL,减少模板代码。
类型安全:编译时检查字段类型,避免 SQL 语法错误。
可移植性:ORM 抽象了底层 SQL 方言,切换数据库(MySQL → PostgreSQL)时改动较小。
GORM 是 Go 中最流行的 ORM 库,目前维护版本为 v2,具有以下核心特点:
二、安装与连接 MySQL
2.1 安装 GORM 和 MySQL 驱动
# 安装 GORMgo get-ugorm.io/gorm# 安装 MySQL 驱动go get-ugorm.io/driver/mysql2.2 连接 MySQL
在 Go 中连接 MySQL 需要构建 DSN(Data Source Name,数据源名称) 字符串:
packagemainimport("gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger")funcmain(){// DSN: 用户名:密码@tcp(主机:端口)/数据库名?charset=utf8mb4&parseTime=True&loc=Localdsn:="root:123456@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"// 连接数据库db,err:=gorm.Open(mysql.Open(dsn),&gorm.Config{Logger:logger.Default.LogMode(logger.Info),// 打印 SQL 日志})iferr!=nil{panic("连接数据库失败: "+err.Error())}// 获取底层的 *sql.DB,可以配置连接池sqlDB,_:=db.DB()sqlDB.SetMaxIdleConns(10)// 设置空闲连接池中的最大连接数sqlDB.SetMaxOpenConns(100)// 设置数据库连接最大打开数sqlDB.SetConnMaxLifetime(time.Hour)// 设置连接可复用的最大时间fmt.Println("数据库连接成功!")}⚠️ 注意:DSN 中的 parseTime=True 使 GORM 自动将 MySQL 的 DATETIME 类型映射为 Go 的 time.Time,charset=utf8mb4 支持完整的 Unicode(包括 emoji)。
三、模型定义与标签
在 GORM 中,数据库表通过 Go 结构体来定义。结构体中的字段通过标签(Tag) 来控制数据库映射行为。
3.1 基础模型
packagemodelsimport("gorm.io/gorm""time")// User 用户模型typeUserstruct{IDuint`gorm:"primaryKey" json:"id"`Namestring`gorm:"size:100;not null" json:"name"`Emailstring`gorm:"size:100;uniqueIndex;not null" json:"email"`Ageint`gorm:"default:0" json:"age"`CreatedAt time.Time`json:"created_at"`UpdatedAt time.Time`json:"updated_at"`DeletedAt gorm.DeletedAt`gorm:"index" json:"-"`}3.2 常用标签

3.3 嵌入 gorm.Model
GORM 提供了预定义的 gorm.Model,包含 ID、CreatedAt、UpdatedAt、DeletedAt 四个常用字段:
typeModelstruct{IDuint`gorm:"primaryKey"`CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt`gorm:"index"`}可以直接嵌入到你的模型中:
typeUserstruct{gorm.Model NamestringEmailstring}四、自动迁移(Auto Migration)
GORM 的自动迁移功能可以根据结构体自动创建或更新表结构:
funcmain(){db,err:=gorm.Open(mysql.Open(dsn),&gorm.Config{})iferr!=nil{panic(err)}// 自动迁移(创建表、添加字段、添加索引)db.AutoMigrate(&User{})// 可以同时迁移多个模型// db.AutoMigrate(&User{}, &Order{}, &Product{})}⚠️ 注意:自动迁移不会删除未使用的列,也不会修改列的类型(在部分数据库中可能受限)。在生产环境中,建议使用数据库迁移工具(如 goose、golang-migrate)来管理 schema 变更。
五、CRUD 操作
5.1 创建(Create)
单条记录创建:
funccreateUser(db*gorm.DB){user:=User{Name:"张三",Email:"zhangsan@example.com",Age:28,}result:=db.Create(&user)ifresult.Error!=nil{fmt.Println("创建失败:",result.Error)return}fmt.Printf("创建成功,用户 ID: %d\n",user.ID)}批量创建:
users:=[]User{{Name:"李四",Email:"lisi@example.com",Age:22},{Name:"王五",Email:"wangwu@example.com",Age:25},}db.Create(&users)创建时选择特定字段:
db.Select("Name","Email").Create(&user)5.2 查询(Read)
查询单条记录:
varuser User// 根据主键查询db.First(&user,1)// SELECT * FROM users WHERE id = 1;// 根据条件查询db.First(&user,"email = ?","zhangsan@example.com")查询多条记录:
varusers[]User// 查询所有db.Find(&users)// 条件查询db.Where("age > ?",20).Find(&users)// 链式查询db.Where("age > ?",18).Order("age desc").Limit(10).Find(&users)查询特定字段:
varusers[]User db.Select("name","email").Find(&users)判断记录是否存在:
varuser User result:=db.First(&user,"email = ?","notexists@example.com")ifresult.Error==gorm.ErrRecordNotFound{fmt.Println("记录不存在")}5.3 更新(Update)
更新单条记录(先查询再更新,推荐):
varuser User db.First(&user,1)user.Name="张三改"user.Age=30db.Save(&user)// 保存所有字段更新指定字段:
db.Model(&User{}).Where("id = ?",1).Update("name","新名字")// 更新多个字段db.Model(&User{}).Where("id = ?",1).Updates(User{Name:"新名字",Age:30,})// 或用 mapdb.Model(&User{}).Where("id = ?",1).Updates(map[string]interface{}{"name":"新名字","age":30,})更新时忽略特定字段:
db.Model(&user).Omit("created_at").Save(&user)5.4 删除(Delete)
物理删除:
// 根据主键删除db.Delete(&User{},1)// 条件删除db.Where("age < ?",18).Delete(&User{})软删除:
如果模型中包含 gorm.DeletedAt 字段,GORM 默认执行软删除——数据不会从数据库中物理删除,而是将 deleted_at 字段设置为当前时间。查询时会自动忽略被软删除的记录。
// 软删除db.Delete(&User{},1)// 查询时会自动忽略软删除的记录db.Find(&users)// 不会包含 ID 为 1 的记录// 查询包含软删除的记录db.Unscoped().Find(&users)// 永久删除(物理删除)db.Unscoped().Delete(&User{},1)六、实战:用户管理服务
下面是一个整合了 GORM 的完整用户管理 API:
packagemainimport("fmt""net/http""time""github.com/gin-gonic/gin""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger")// ---- 模型 ----typeUserstruct{IDuint`gorm:"primaryKey" json:"id"`Namestring`gorm:"size:100;not null" json:"name"`Emailstring`gorm:"size:100;uniqueIndex;not null" json:"email"`Ageint`json:"age"`CreatedAt time.Time`json:"created_at"`UpdatedAt time.Time`json:"updated_at"`DeletedAt gorm.DeletedAt`gorm:"index" json:"-"`}// ---- 请求结构体 ----typeCreateUserRequeststruct{Namestring`json:"name" binding:"required"`Emailstring`json:"email" binding:"required,email"`Ageint`json:"age" binding:"required,min=1,max=150"`}typeUpdateUserRequeststruct{Namestring`json:"name"`Emailstring`json:"email" binding:"omitempty,email"`Ageint`json:"age" binding:"omitempty,min=1,max=150"`}// ---- 全局 DB ----vardb*gorm.DB// ---- Handlers ----funccreateUser(c*gin.Context){varreq CreateUserRequestiferr:=c.ShouldBindJSON(&req);err!=nil{c.JSON(http.StatusBadRequest,gin.H{"error":err.Error()})return}user:=User{Name:req.Name,Email:req.Email,Age:req.Age,}iferr:=db.Create(&user).Error;err!=nil{c.JSON(http.StatusInternalServerError,gin.H{"error":"创建用户失败: "+err.Error()})return}c.JSON(http.StatusCreated,user)}funcgetUsers(c*gin.Context){varusers[]User db.Find(&users)c.JSON(http.StatusOK,users)}funcgetUser(c*gin.Context){id:=c.Param("id")varuser Useriferr:=db.First(&user,id).Error;err!=nil{c.JSON(http.StatusNotFound,gin.H{"error":"用户不存在"})return}c.JSON(http.StatusOK,user)}funcupdateUser(c*gin.Context){id:=c.Param("id")varreq UpdateUserRequestiferr:=c.ShouldBindJSON(&req);err!=nil{c.JSON(http.StatusBadRequest,gin.H{"error":err.Error()})return}// 使用 map 动态更新updates:=make(map[string]interface{})ifreq.Name!=""{updates["name"]=req.Name}ifreq.Email!=""{updates["email"]=req.Email}ifreq.Age>0{updates["age"]=req.Age}iflen(updates)==0{c.JSON(http.StatusBadRequest,gin.H{"error":"没有需要更新的字段"})return}result:=db.Model(&User{}).Where("id = ?",id).Updates(updates)ifresult.RowsAffected==0{c.JSON(http.StatusNotFound,gin.H{"error":"用户不存在"})return}c.JSON(http.StatusOK,gin.H{"message":"更新成功"})}funcdeleteUser(c*gin.Context){id:=c.Param("id")result:=db.Delete(&User{},id)ifresult.RowsAffected==0{c.JSON(http.StatusNotFound,gin.H{"error":"用户不存在"})return}c.Status(http.StatusNoContent)}funcmain(){// 连接数据库dsn:="root:123456@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"varerrerrordb,err=gorm.Open(mysql.Open(dsn),&gorm.Config{Logger:logger.Default.LogMode(logger.Info),})iferr!=nil{panic("连接数据库失败: "+err.Error())}// 自动迁移iferr:=db.AutoMigrate(&User{});err!=nil{panic("迁移失败: "+err.Error())}// 设置 Ginr:=gin.Default()api:=r.Group("/api/v1/users"){api.POST("/",createUser)api.GET("/",getUsers)api.GET("/:id",getUser)api.PUT("/:id",updateUser)api.DELETE("/:id",deleteUser)}fmt.Println("服务器启动在 :8080")r.Run(":8080")}七、小结
GORM:Go 生态最流行的 ORM 库,通过结构体和方法操作数据库,避免手写 SQL。
安装与连接:go get gorm.io/gorm + gorm.io/driver/mysql,DSN 配置连接参数。
模型定义:使用结构体标签(gorm:“…”)控制字段映射、约束和索引。
自动迁移:db.AutoMigrate(&Model{}) 自动创建/更新表结构。
CRUD 操作:Create(创建)、First/Find(查询)、Save/Updates(更新)、Delete(删除,支持软删除)。