一、模型定义:结构体映射数据表
1. 基础规范
-
约定:表结构体后缀统一
Model,直观区分普通业务结构体与数据库模型; -
一对一映射关系:
- 一个
XXXModel结构体 = 数据库一张数据表 - 结构体字段 = 数据表列
- 结构体实例 = 数据表单条记录
- 一个
-
字段标签:所有约束、列名、索引、注释通过
gorm:"xxx"标签配置,官方文档:
2. 基础模型示例(带完整标签注释)
import "time"
// UserModel 用户表模型
type UserModel struct {
ID int64 `gorm:"primaryKey;autoIncrement;comment:自增主键ID"`
Name string `gorm:"column:username;not null;unique;size:50;comment:用户名,唯一非空"`
Age int `gorm:"default:18;comment:年龄,默认18岁"`
CreatedAt time.Time `gorm:"comment:创建时间,GORM自动维护"`
UpdatedAt time.Time `gorm:"comment:更新时间,GORM自动维护"`
}
// 固定表名(解决GORM默认复数表名问题:UserModel → user_models)
func (UserModel) TableName() string {
return "user"
}
常用标签说明
表格
| 标签 | 作用 |
|---|---|
| primaryKey | 标记为主键 |
| autoIncrement | 自增主键 |
| column:xxx | 手动指定数据库列名 |
| not null | 字段非空约束 |
| unique | 唯一索引 |
| size:50 | 字符串长度限制 |
| default:xxx | 设置字段默认值 |
| comment | 字段注释 |
3. 内置通用基类 gorm.Model(简化开发)
GORM 提供内置结构体,自带 ID、CreatedAt、UpdatedAt、DeletedAt,开箱即用:
import "gorm.io/gorm"
type UserModel struct {
gorm.Model // 内置:ID, CreatedAt, UpdatedAt, DeletedAt
Name string `gorm:"not null;unique;size:50"`
Age int `gorm:"default:18"`
}
DeletedAt存在时,自动开启软删除功能。
二、自动建表 AutoMigrate(自动迁移)
1. 作用
根据模型结构体自动创建 / 更新数据表,仅新增字段、索引,不会删除原有列、不会修改字段类型,适合开发环境;生产环境建议手写 SQL 迁移脚本。
2. 完整可运行代码
package main
import (
"fmt"
"log"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type UserModel struct {
ID int64 `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"not null;unique;size:50"`
Age int `gorm:"default:18"`
CreatedAt time.Time
UpdatedAt time.Time
}
// 指定表名
func (UserModel) TableName() string {
return "user"
}
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/gorm_new_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("数据库连接失败:", err)
}
// 自动迁移建表
err = db.AutoMigrate(&UserModel{})
if err != nil {
log.Fatal("自动建表失败:", err)
}
fmt.Println("数据表自动生成/更新成功")
}
补充特性
- 新增字段:结构体新增字段 → 数据库自动追加列;
- 新增索引:结构体添加
unique/index标签 → 自动创建索引; - 限制:不删除字段、不修改字段类型、不删除索引;
- 支持一次性迁移多张表:
db.AutoMigrate(&UserModel{}, &OrderModel{})。
三、新增数据 Create(单条 / 批量)
核心规则
Create()参数必须传入结构体指针 / 切片指针;- 创建成功后,主键、自动维护时间会回填到原结构体;
- 字段标签
not null、unique冲突会直接抛出 SQL 错误。
1. 单条插入
// 单条新增
func InsertSingle(db *gorm.DB) {
user := UserModel{
Name: "张三",
Age: 20,
}
// 传入结构体指针
err := db.Create(&user).Error
if err != nil {
log.Println("插入失败:", err)
return
}
// user.ID、CreatedAt、UpdatedAt 已自动回填
fmt.Printf("新增成功,主键ID:%d\n", user.ID)
}
2. 批量插入
func InsertBatch(db *gorm.DB) {
userList := []UserModel{
{Name: "李四", Age: 19},
{Name: "王五", Age: 22},
{Name: "赵六", Age: 21},
}
// 传入切片指针
err := db.Create(&userList).Error
if err != nil {
log.Println("批量插入失败:", err)
return
}
fmt.Println("批量插入完成,数据已回填ID")
}
3. 创建钩子 BeforeCreate
插入数据前自动执行,可预处理字段;返回 error 直接终止插入,数据不入库。
// 创建钩子
func (u *UserModel) BeforeCreate(tx *gorm.DB) error {
fmt.Println("执行创建钩子 BeforeCreate")
// 统一处理用户名
u.Name = "前缀_" + u.Name
// 返回错误则插入失败
// return errors.New("禁止插入")
return nil
}
四、查询数据(基础查询 + 条件 + 判空 + 打印 SQL)
1. 查询全部数据 Find
func FindAll(db *gorm.DB) {
var list []UserModel
// 查询user表全部数据
err := db.Find(&list).Error
if err != nil {
log.Println("查询全部失败:", err)
return
}
fmt.Printf("全部数据:%+v\n", list)
}
2. 带条件查询 Find
func FindByCondition(db *gorm.DB) {
var list []UserModel
// 占位符防SQL注入,? 对应参数
err := db.Find(&list, "name = ? AND age >= ?", "张三", 18).Error
if err != nil {
log.Println("条件查询失败:", err)
return
}
fmt.Println("条件匹配数据:", list)
}
3. 查询单条 Take / First
-
First(&u):按主键升序取第一条,无数据返回gorm.ErrRecordNotFound -
Take(&u):随机取一条,无数据返回gorm.ErrRecordNotFound -
Last(&u):按主键降序取第一条func FindOne(db *gorm.DB) {
var user UserModel
// 根据ID查询单条
err := db.Take(&user, "id = ?", 100).Error
if err != nil {
// 判断无记录错误
if err == gorm.ErrRecordNotFound {
fmt.Println("未查询到匹配记录")
return
}
log.Println("查询单条异常:", err)
return
}
fmt.Printf("单条数据:%+v\n", user)
}
4. 不抛错的单条查询(Limit+Find)
找不到数据不会返回 ErrRecordNotFound,仅结构体空值
var user UserModel
err := db.Limit(1).Find(&user, "id = ?", 100).Error
5. Debug 打印执行 SQL(调试必备)
// Debug() 打印完整执行SQL、耗时、影响行数
db.Debug().Take(&user, "id = ?", 1)
五、更新操作:Save / Update / UpdateColumn / Updates 区别
核心区分总览
表格
| 方法 | 特性 | 零值更新 | 触发 Hook | 适用场景 |
|---|---|---|---|---|
| Save | 有主键 = 更新,无主键 = 新增;全字段覆盖 | 支持 | 是 | 完整保存模型 |
| Update | 更新单个字段,必须加 Where 条件 | 支持 | 是 | 单个字段修改 |
| UpdateColumn | 更新单个字段,不执行钩子 | 支持 | 否 | 高性能更新、计数器 |
| Updates | 结构体仅更新非零值;map 可更新零值 | 结构体不支持 /map 支持 | 是 | 多字段批量更新 |
1. Save 保存(新增 / 更新二合一)
func UpdateBySave(db *gorm.DB) {
// 无主键:新增
user1 := UserModel{Name: "枫枫1", Age: 18}
db.Save(&user1)
// 存在主键ID:执行更新,所有字段覆盖写入
user2 := UserModel{
ID: 1,
Name: "枫枫2",
Age: 25,
}
err := db.Save(&user2).Error
if err != nil {
log.Println("Save更新失败:", err)
}
}
// 更新钩子 BeforeUpdate
func (u *UserModel) BeforeUpdate(tx *gorm.DB) error {
fmt.Println("执行更新钩子 BeforeUpdate")
return nil
}
2. Update / UpdateColumn 单字段更新
// Update 触发钩子
err := db.Model(&UserModel{}).
Where("id = ?", 1).
Update("name", "张三新名字").Error
// UpdateColumn 跳过钩子,性能更高
err := db.Model(&UserModel{}).
Where("id = ?", 1).
UpdateColumn("name", "张三新名字").Error
3. Updates 多字段更新
// 方式1:传入结构体,零值不会更新(name=""不会覆盖数据库原值)
user := UserModel{ID: 1}
err := db.Model(&user).Updates(UserModel{
Name: "张三丰",
Age: 30,
}).Error
// 方式2:传入map[string]any,可以更新零值(name置空生效)
err = db.Model(&UserModel{}).
Where("id = ?", 2).
Updates(map[string]any{
"name": "",
"age": 22,
}).Error
4. Expr 表达式更新(自增 / 运算)
不需要读取原数据,直接数据库内计算,常用于点赞、年龄自增
// age = age + 1
err := db.Model(&UserModel{}).
Where("id = ?", 1).
UpdateColumn("age", gorm.Expr("age + ?", 1)).Error
六、删除 Delete(软删除 / 物理删除)
1. 基础删除语法
func DeleteData(db *gorm.DB) {
// 方式1:传入带主键的模型
user := UserModel{ID: 10}
err := db.Delete(&user).Error
// 方式2:直接指定模型+主键
err = db.Delete(&UserModel{}, 9).Error
// 方式3:批量删除多个ID
err = db.Delete(&UserModel{}, []int64{1, 2, 3}).Error
}
// 删除钩子 BeforeDelete
func (u *UserModel) BeforeDelete(tx *gorm.DB) error {
fmt.Println("执行删除钩子 BeforeDelete")
return nil
}
2. 软删除机制(重点)
开启条件
模型包含 gorm.Model(自带 DeletedAt time.Time),自动开启软删除:
- 调用
Delete()不会执行DELETE; - 执行
UPDATE table SET deleted_at = 当前时间 WHERE id=?; - 普通
Find/Take/First查询自动过滤deleted_at IS NOT NULL的数据。
1)查询已软删除的数据(Unscoped)
var list []UserModel
// 忽略软删除标记,查询全部(含已删除)
db.Unscoped().Find(&list)
2)永久物理删除(真正从库中清除)
go
运行
var user UserModel
user.ID = 1
// Unscoped 强制物理删除
db.Unscoped().Delete(&user)
七、通用重要知识点
1. 连接池配置(必配,防止连接溢出)
sqlDB, err := db.DB()
if err == nil {
sqlDB.SetMaxOpenConns(10) // 最大并发连接
sqlDB.SetMaxIdleConns(5) // 空闲保留连接
sqlDB.SetConnMaxLifetime(time.Hour)
sqlDB.SetConnMaxIdleTime(30 * time.Minute)
}
2. 事务操作(复杂增删改保证原子性)
// 开启事务
tx := db.Begin()
// 操作
err := tx.Create(&user).Error
if err != nil {
tx.Rollback() // 失败回滚
return
}
tx.Commit() // 全部成功提交
3. 分页查询 Limit Offset
// 第1页,每页10条
var list []UserModel
db.Limit(10).Offset(0).Find(&list)
4. 排序 Order
// id 降序
db.Order("id desc").Find(&list)
5. 计数 Count
var total int64
db.Model(&UserModel{}).Where("age >= ?", 18).Count(&total)
fmt.Println("符合条件总条数:", total)
八、开发避坑总结
- 表名坑:GORM 默认结构体转复数表名,必须自定义
TableName()修正; - 软删除坑:有
DeletedAt时 Delete 仅标记,真正删除要加Unscoped(); - Updates 零值坑:结构体传参不会更新空字符串、0 等零值,map 传参才可以;
- 钩子坑:
UpdateColumn、Delete(Unscoped)不会触发更新 / 删除钩子; - 新增查询坑:
Take/First查无数据会报错,需要手动判断gorm.ErrRecordNotFound; - AutoMigrate 坑:仅新增字段,不删除、不改字段类型,生产环境禁止依赖。
九、综合实力
package main
import (
"errors"
"fmt"
"log"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// User 用户模型,内置gorm.Model实现软删除、创建更新时间
type User struct {
gorm.Model // ID uint; CreatedAt,UpdatedAt,DeletedAt time.Time
Name string `gorm:"size:32;not null;comment:用户名"`
Age int `gorm:"comment:年龄"`
Email string `gorm:"size:64;unique;comment:邮箱"`
Password string `gorm:"size:64;comment:密码"`
Score float64 `gorm:"comment:分数"`
}
func (User) TableName() string {
return "usersdb" // 指定表名为users,默认是user
}
// 全局数据库实例
var db *gorm.DB
// initDB 初始化数据库连接
func initDB() {
// DSN配置,修改为自己的mysql账号密码库名
dsn := "root:123456@tcp(127.0.0.1:3306)/godb?charset=utf8mb4&parseTime=True&loc=Local"
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
// 打印完整SQL日志
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
log.Fatalf("数据库连接失败: %v", err)
}
// 自动建表/同步字段
err = db.AutoMigrate(&User{})
if err != nil {
log.Fatalf("自动迁移表失败: %v", err)
}
fmt.Println("=== 数据库连接成功,数据表同步完成 ===")
}
// createData 新增数据:单条、批量
func createData() {
fmt.Println("\n===== 新增数据 =====")
// 1. 单条插入
u1 := User{
Name: "张三",
Age: 20,
Email: "zhangsan@demo.com",
Password: "123123",
Score: 90.5,
}
res := db.Create(&u1)
if res.Error != nil {
log.Printf("单条新增失败: %v", res.Error)
} else {
fmt.Printf("单条新增成功,用户ID: %d, 影响行数: %d\n", u1.ID, res.RowsAffected)
}
// 2. 批量插入
userList := []User{
{Name: "李四", Age: 22, Email: "lisi@demo.com", Password: "456456", Score: 88},
{Name: "王五", Age: 25, Email: "wangwu@demo.com", Password: "789789", Score: 95.2},
{Name: "赵六", Age: 19, Email: "zhaoliu@demo.com", Password: "666666", Score: 76},
}
batchRes := db.Create(&userList)
fmt.Printf("批量新增成功,插入条数: %d\n", batchRes.RowsAffected)
}
// queryData 查询各类场景示例
func queryData() {
fmt.Println("\n===== 查询数据 =====")
// 1. 根据主键ID查询第一条
var user1 User
err := db.First(&user1, 1).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
fmt.Println("ID=1 用户不存在")
} else {
log.Printf("查询ID失败: %v", err)
}
} else {
fmt.Printf("根据ID查询: %+v\n", user1)
}
// 2. 条件查询 Where 等值
var user2 User
db.Where("name = ?", "李四").First(&user2)
fmt.Printf("按姓名查询李四: %+v\n", user2)
// 3. 多条件 AND 查询
var list1 []User
db.Where("age > ? AND score > ?", 20, 80).Find(&list1)
fmt.Println("年龄大于20且分数大于80的用户:")
for _, item := range list1 {
fmt.Printf(" %+v\n", item)
}
// 4. IN / OR / 分页、排序
var list2 []User
db.Where("age IN (?)", []int{19, 22}).
Or("score > 90").
Order("score DESC").
Limit(2).Offset(0). // 分页:每页2条,第1页
Find(&list2)
fmt.Println("IN+OR+排序+分页结果:")
for _, item := range list2 {
fmt.Printf(" %s 分数:%.1f\n", item.Name, item.Score)
}
// 5. 只查询指定字段
var list3 []User
db.Select("name", "age", "score").Where("age < 25").Find(&list3)
fmt.Println("只查询姓名年龄分数:", list3)
// 6. 统计总数
var count int64
db.Model(&User{}).Where("score > 80").Count(&count)
fmt.Printf("分数大于80的总人数: %d\n", count)
// 7. 查询包含已软删除数据 Unscoped
var allWithDelete []User
db.Unscoped().Find(&allWithDelete)
fmt.Printf("包含已删除数据总条数: %d\n", len(allWithDelete))
}
// updateData 更新操作:单列、多列、Save全量更新
func updateData() {
fmt.Println("\n===== 更新数据 =====")
// 1. 更新单个字段
res1 := db.Model(&User{}).Where("name = ?", "张三").Update("age", 21)
fmt.Printf("更新单字段影响行数: %d\n", res1.RowsAffected)
// 2. map更新多字段(零值也会更新)
res2 := db.Model(&User{}).Where("email = ?", "lisi@demo.com").
Updates(map[string]interface{}{
"age": 23,
"score": 92.0,
})
fmt.Printf("map多字段更新影响行数: %d\n", res2.RowsAffected)
// 3. Save全量更新(先查询,所有字段覆盖更新)
var wangwu User
db.Where("name = ?", "王五").First(&wangwu)
wangwu.Score = 98.5
wangwu.Password = "newpwd123"
db.Save(&wangwu)
fmt.Printf("Save全量更新王五完成,新分数:%.1f\n", wangwu.Score)
}
// deleteData 删除:软删除、物理删除
func deleteData() {
fmt.Println("\n===== 删除数据 =====")
// 1. 软删除(默认,仅更新deleted_at)
resDel := db.Delete(&User{}, 4) // 删除ID=4(赵六)
fmt.Printf("软删除影响行数: %d\n", resDel.RowsAffected)
// 2. 物理删除,Unscoped真正从表移除数据
// var delUser User
// db.Where("name = ?", "赵六").Unscoped().Delete(&delUser)
// fmt.Println("物理删除赵六完成")
}
// transactionDemo 事务示例
func transactionDemo() {
fmt.Println("\n===== 事务操作示例 =====")
tx := db.Begin() // 开启事务
if tx.Error != nil {
log.Fatal("开启事务失败:", tx.Error)
}
// 事务内执行新增
newUser := User{Name: "事务测试", Age: 24, Email: "tx@demo.com", Password: "tx123", Score: 85}
if err := tx.Create(&newUser).Error; err != nil {
tx.Rollback() // 出错回滚
log.Fatal("事务插入失败,已回滚:", err)
}
// 事务内更新
if err := tx.Model(&User{}).Where("name = ?", "张三").Update("score", 99).Error; err != nil {
tx.Rollback()
log.Fatal("事务更新失败,已回滚:", err)
}
// 提交事务
err := tx.Commit().Error
if err != nil {
tx.Rollback()
log.Fatal("提交事务失败:", err)
}
fmt.Println("事务执行成功,已提交")
}
func main() {
// 初始化数据库
initDB()
// 1. 增
createData()
// 2. 查
queryData()
// 3. 改
updateData()
// 4. 删
deleteData()
// 5. 事务演示
transactionDemo()
fmt.Println("\n===== 全部CRUD执行完成 =====")
}