4-数据库表语go中模型对应关系

一、模型定义:结构体映射数据表

1. 基础规范

  1. 约定:表结构体后缀统一 Model,直观区分普通业务结构体与数据库模型;

  2. 一对一映射关系:

    • 一个 XXXModel 结构体 = 数据库一张数据表
    • 结构体字段 = 数据表列
    • 结构体实例 = 数据表单条记录
  3. 字段标签:所有约束、列名、索引、注释通过 gorm:"xxx" 标签配置,官方文档:

    https://gorm.io/zh_CN/docs/models.html

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("数据表自动生成/更新成功")
}

补充特性

  1. 新增字段:结构体新增字段 → 数据库自动追加列;
  2. 新增索引:结构体添加 unique/index 标签 → 自动创建索引;
  3. 限制:不删除字段、不修改字段类型、不删除索引
  4. 支持一次性迁移多张表:db.AutoMigrate(&UserModel{}, &OrderModel{})

三、新增数据 Create(单条 / 批量)

核心规则

  1. Create() 参数必须传入结构体指针 / 切片指针
  2. 创建成功后,主键、自动维护时间会回填到原结构体
  3. 字段标签 not nullunique 冲突会直接抛出 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),自动开启软删除:

  1. 调用 Delete() 不会执行 DELETE
  2. 执行 UPDATE table SET deleted_at = 当前时间 WHERE id=?
  3. 普通 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)

八、开发避坑总结

  1. 表名坑:GORM 默认结构体转复数表名,必须自定义 TableName() 修正;
  2. 软删除坑:有 DeletedAt 时 Delete 仅标记,真正删除要加 Unscoped()
  3. Updates 零值坑:结构体传参不会更新空字符串、0 等零值,map 传参才可以;
  4. 钩子坑:UpdateColumnDelete(Unscoped) 不会触发更新 / 删除钩子;
  5. 新增查询坑:Take/First 查无数据会报错,需要手动判断 gorm.ErrRecordNotFound
  6. 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执行完成 =====")
}