目标: golang gorm 操作本地数据库的增删改查
其中gorm全程是golang object relations mapping
参考文章
这个全一点:https://blog.csdn.net/m0_66100833/article/details/132920354
这个短一点:https://juejin.cn/post/7262554603488837689
一、连接并打开数据库
- 相关的包
gorm.io/gorm
gorm.io/driver/mysql
- 连接数据库
// 定义数据库连接字符串
//user是数据库用户名
//password是数据库密码
//tcp是连接方式,里面放了端口号
//dbname是数据库名称
//如果是本地数据库,端口S号一般是3306
// 如果是远程数据库,端口号可能是其他值
//字符集设置为utf8mb4,支持中文和emoji表情
//loc设置时区
dsn := "user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
二、mysql数据表与结构体映射关系
| go | mysql |
|---|---|
| 结构体 | 数据表 |
| 结构体字段 | 字段(列) |
| 带primaryKey标签的字段 | 主键 |
| 带index标签的字段 | 索引 |
| 结构体实例 | 记录(行) |
- 通过标签修改字段名和字段数据类型
type Student struct {
ID int32
Name string
age int
ExternalCharacter string `gorm:"column:externalCharacter; size:255"`
}
三、table的创建
【1】db.AutoMigrate方法
一种自动创建和迁移数据库表的方法。会根据gorm模型定义,自动检查数据库表是否存在:
- 不存在则创建表
- 存在,若表结构有变化则进行迁移。
- 会新增缺失的字段和索引
- 不会删除已有的字段(避免数据丢失)
- 不会修改已有字段的类型(避免数据丢失)
适用于开发过程中快速迭代和维护数据结构的情况
// 自动创建和迁移表
err := db.AutoMigrate(&Student{})
// 这里的db就是上述gorm.open的db哈
if err != nil {
panic("创建/迁移表格失败, error = " + err.Error())
}
【2】db.Migrator().CreateTable()方法
手动创建数据库表的方式。需要显式制定要创建的表,不会检查是否已经存在,而是直接创建表。
四、增
【1】db.Create(结构体对象)
lisi := Student{2, "lisi", 18, "modest"}
result := db.Create(&lisi)
if result.Error != nil {
panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1
【2】db.select(指定字段).Create(结构体对象)
lisi := Student{2, "lisi", 18, "modest"}
result := db.Select("ID", "Name", "Age").Create(&lisi)
if result.Error != nil {
panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1
【3】db.Omit(忽略字段).Create(结构体对象)
【4】原生mysql语句
lisi := Student{2, "lisi", 18, "modest"}
result := db.Exec("INSERT INTO `student` (`name`,`age`,`externalCharacter`,`id`) VALUES ('lisi',18,'modest',2)")
if result.Error != nil {
panic("插入字段失败, error = " + result.Error.Error())
}
fmt.Println(result.RowsAffected) // 返回影响的行数 1
五、查
GORM提供了丰富的查找方法,可以根据不同需求从数据库中检索数据。以下是常用的查找方法及示例:
1. 基础查询方法
First:查询第一条记录
var user User
db.First(&user) // 查询主键排序后的第一条记录
// 等价SQL: SELECT * FROM users ORDER BY id ASC LIMIT 1;
Last:查询最后一条记录
db.Last(&user) // 查询主键排序后的最后一条记录
// 等价SQL: SELECT * FROM users ORDER BY id DESC LIMIT 1;
Find:查询多条记录
var users []User
db.Find(&users) // 查询所有记录
// 等价SQL: SELECT * FROM users;
Take:随机获取一条记录
db.Take(&user) // 不保证顺序,随机获取一条
// 等价SQL: SELECT * FROM users LIMIT 1;
2. 条件查询
按主键查询
db.First(&user, 1) // 查询ID=1的记录
// 等价SQL: SELECT * FROM users WHERE id = 1 LIMIT 1;
按字段查询
// 单个条件
db.Where("name = ?", "张三").First(&user)
// 等价SQL: SELECT * FROM users WHERE name = '张三' LIMIT 1;
// 多个条件
db.Where("name = ? AND age > ?", "张三", 20).Find(&users)
// 等价SQL: SELECT * FROM users WHERE name = '张三' AND age > 20;
结构体条件
db.Where(&User{Name: "张三", Age: 20}).First(&user)
// 等价SQL: SELECT * FROM users WHERE name = '张三' AND age = 20 LIMIT 1;
映射条件
db.Where(map[string]interface{}{"name": "张三", "age": 20}).Find(&users)
// 等价SQL: SELECT * FROM users WHERE name = '张三' AND age = 20;
3. 高级查询
范围查询
// IN条件
db.Where("age IN ?", []int{20, 21, 22}).Find(&users)
// 等价SQL: SELECT * FROM users WHERE age IN (20, 21, 22);
// BETWEEN条件
db.Where("age BETWEEN ? AND ?", 20, 30).Find(&users)
// 等价SQL: SELECT * FROM users WHERE age BETWEEN 20 AND 30;
模糊查询
db.Where("name LIKE ?", "%三%").Find(&users)
// 等价SQL: SELECT * FROM users WHERE name LIKE '%三%';
排序与分页
// 排序
db.Order("age DESC").Find(&users) // 按年龄降序
// 等价SQL: SELECT * FROM users ORDER BY age DESC;
// 分页
db.Limit(10).Offset(20).Find(&users) // 第21-30条记录
// 等价SQL: SELECT * FROM users LIMIT 10 OFFSET 20;
计数与聚合
var count int64
db.Model(&User{}).Count(&count) // 统计总数
// 等价SQL: SELECT count(*) FROM users;
// 聚合函数
var totalAge int64
db.Model(&User{}).Select("SUM(age)").Row().Scan(&totalAge)
// 等价SQL: SELECT SUM(age) FROM users;
4. 关联查询
假设User有多个Article:
type User struct {
gorm.Model
Name string
Articles []Article
}
type Article struct {
gorm.Model
Title string
UserID uint
}
预加载关联
db.Preload("Articles").First(&user, 1)
// 查询用户及其所有文章
带条件的关联查询
db.Preload("Articles", "status = ?", "published").First(&user, 1)
// 只加载已发布的文章
5. 原生SQL查询
// 原生查询
db.Raw("SELECT * FROM users WHERE name = ?", "张三").Scan(&user)
// 执行SQL
db.Exec("DELETE FROM users WHERE age < ?", 18)
6. 查询链
GORM支持链式调用,构建复杂查询:
db.Where("name LIKE ?", "%三%")
.Or("age > ?", 30)
.Order("created_at DESC")
.Limit(5)
.Find(&users)
注意事项
- 零值问题:结构体条件查询会忽略零值字段(如
0、""、false),改用map条件可避免 - 软删除:使用
gorm.Model的模型默认支持软删除,已删除记录不会被查询 - 错误处理:查询结果需检查错误,如
db.First(&user).Error - 性能优化:复杂查询建议使用原生SQL或手动构建查询
六、删除
在 GORM 中删除数据主要有两种方式:软删除(逻辑删除)和硬删除(物理删除),具体操作如下:
【1】、软删除(推荐,默认支持)
适用场景
- 需要保留数据记录,但标记为“已删除”状态
- 配合
gorm.Model中的DeletedAt字段使用
前提条件
模型需嵌入 gorm.Model(包含 DeletedAt 字段):
type User struct {
gorm.Model // 包含 ID、CreatedAt、UpdatedAt、DeletedAt
Name string
Age int
}
基础删除操作
var user User
// 1. 先查询要删除的记录(根据 ID 查询)
db.First(&user, 1) // 查询 ID=1 的用户
// 2. 执行软删除(默认行为)
result := db.Delete(&user)
if result.Error != nil {
log.Fatalf("删除失败: %v", result.Error)
return
}
log.Printf("软删除成功,删除记录数: %d", result.RowsAffected)
执行的 SQL
UPDATE users SET deleted_at = '2025-06-03 15:30:00' WHERE id = 1 AND deleted_at IS NULL;
【2】、硬删除(物理删除)
适用场景
- 彻底删除数据(无法通过常规查询找回)
- 需要绕过软删除机制
操作方法
使用 Unscoped() 方法禁用软删除:
var user User
// 1. 查询记录(硬删除无需考虑软删除状态)
db.Unscoped().First(&user, 1) // 或直接查询
// 2. 执行硬删除
result := db.Unscoped().Delete(&user)
if result.Error != nil {
log.Fatalf("硬删除失败: %v", result.Error)
return
}
log.Printf("硬删除成功,删除记录数: %d", result.RowsAffected)
执行的 SQL
DELETE FROM users WHERE id = 1;
【3】、批量删除
按条件软删除
// 删除所有年龄 < 18 的用户(软删除)
result := db.Where("age < ?", 18).Delete(&User{})
log.Printf("批量软删除结果: %v, 影响行数: %d", result.Error, result.RowsAffected)
按条件硬删除
// 硬删除所有邮箱包含 "test.com" 的用户
result := db.Unscoped().Where("email LIKE ?", "%test.com%").Delete(&User{})
【4】、恢复软删除记录
适用场景
- 需要还原被软删除的记录
- 仅对软删除的记录有效
操作方法
var user User
// 1. 查询被软删除的记录(需使用 Unscoped 或指定 DeletedAt 条件)
db.Unscoped().First(&user, 1) // 查询 ID=1 的软删除记录
// 2. 恢复记录(清空 DeletedAt 字段)
result := db.Restore(&user)
if result.Error != nil {
log.Fatalf("恢复失败: %v", result.Error)
return
}
log.Println("恢复成功")
执行的 SQL
UPDATE users SET deleted_at = NULL WHERE id = 1 AND deleted_at IS NOT NULL;
【5】、注意事项
-
软删除的查询限制
常规查询(如Find/First)会自动过滤掉DeletedAt非空的记录,如需查询软删除记录,需使用Unscoped:// 查询所有记录(包括软删除) var users []User db.Unscoped().Find(&users) -
硬删除的危险性
硬删除会永久删除数据,执行前务必确认条件正确,建议先通过Where条件验证数据范围。 -
钩子函数与事务
删除操作会触发 GORM 的钩子函数(如BeforeDelete/AfterDelete),可用于自定义逻辑(如日志记录)。
复杂删除操作建议包裹在事务中:tx := db.Begin() defer tx.Rollback() // 失败时回滚 if err := tx.Delete(&user1).Error; err != nil { return err } if err := tx.Delete(&user2).Error; err != nil { return err } return tx.Commit().Error
示例代码整合
func deleteUserExample(db *gorm.DB) {
// 软删除示例
var userToDelete User
db.First(&userToDelete, 1) // 查询待删除记录
if err := db.Delete(&userToDelete).Error; err != nil {
log.Fatal("软删除失败:", err)
}
log.Println("软删除成功")
// 硬删除示例
var hardDeleteUser User
db.Unscoped().First(&hardDeleteUser, 2) // 查询记录(忽略软删除状态)
if err := db.Unscoped().Delete(&hardDeleteUser).Error; err != nil {
log.Fatal("硬删除失败:", err)
}
log.Println("硬删除成功")
// 恢复软删除示例
var restoredUser User
db.Unscoped().First(&restoredUser, 1) // 查询软删除记录
if err := db.Restore(&restoredUser).Error; err != nil {
log.Fatal("恢复失败:", err)
}
log.Println("恢复成功")
}
更改数据
在 GORM 中实现数据更新(修改)主要有以下几种方式,下面为你详细介绍并提供示例代码:
【1】、更新单个记录
1. 全量更新(更新所有字段)
// 先查询要更新的记录
var user User
db.First(&user, 1) // 根据 ID 查询
// 修改字段值
user.Name = "新名字"
user.Age = 31
// 执行更新(会更新所有字段,包括零值字段)
result := db.Save(&user)
if result.Error != nil {
log.Fatalf("更新失败: %v", result.Error)
}
log.Printf("更新成功,影响行数: %d", result.RowsAffected)
2. 选择性更新(只更新指定字段)
var user User
db.First(&user, 1)
// 方式一:使用 Select 指定要更新的字段
db.Select("Name", "Age").Updates(User{Name: "新名字", Age: 31})
// 方式二:使用 Omit 忽略不需要更新的字段
db.Omit("Email").Updates(User{Name: "新名字", Age: 31})
【2】、条件更新(批量更新)
1. 根据条件更新多条记录
// 将所有年龄大于 30 的用户的状态设为 "VIP"
result := db.Model(&User{}).Where("age > ?", 30).Update("status", "VIP")
if result.Error != nil {
log.Fatalf("批量更新失败: %v", result.Error)
}
log.Printf("批量更新成功,影响行数: %d", result.RowsAffected)
2. 批量更新多个字段
// 将名字为 "Alice" 的用户的年龄增加 1,状态设为 "活跃"
result := db.Model(&User{}).Where("name = ?", "Alice").Updates(map[string]interface{}{
"age": gorm.Expr("age + ?", 1), // 使用表达式
"status": "活跃",
})
【3】、高级更新技巧
1. 使用 SQL 表达式
// 原子性地增加某个字段的值(避免并发问题)
db.Model(&User{}).Where("id = ?", 1).Update("age", gorm.Expr("age + ?", 1))
2. 零值字段的更新
// 默认情况下,零值(如 0、""、false)不会被更新
// 若需要更新零值,使用 map 方式:
db.Model(&user).Updates(map[string]interface{}{
"age": 0, // 会更新为 0
"name": "", // 会更新为空字符串
"is_vip": false, // 会更新为 false
})
【4】、完整示例代码
func updateExample(db *gorm.DB) {
// 1. 查询要更新的记录
var user User
if err := db.First(&user, 1).Error; err != nil {
log.Fatalf("查询失败: %v", err)
}
log.Printf("更新前: %+v", user)
// 2. 方式一:全量更新(会更新所有字段,包括零值)
user.Name = "李四"
user.Age = 0 // 零值
if err := db.Save(&user).Error; err != nil {
log.Fatalf("更新失败: %v", err)
}
log.Printf("全量更新后: %+v", user)
// 3. 方式二:选择性更新(只更新非零值字段)
if err := db.Model(&user).Updates(User{
Name: "王五",
Email: "wangwu@example.com",
}).Error; err != nil {
log.Fatalf("选择性更新失败: %v", err)
}
// 此时 Age 字段不会被修改
// 4. 方式三:使用 map 更新(可包含零值)
if err := db.Model(&user).Updates(map[string]interface{}{
"Age": 25,
"Name": "", // 会更新为空字符串
}).Error; err != nil {
log.Fatalf("map 更新失败: %v", err)
}
// 5. 批量更新示例
result := db.Model(&User{}).Where("age < ?", 30).Update("status", "普通用户")
log.Printf("批量更新影响行数: %d", result.RowsAffected)
}
【5】、注意事项
-
先查询后更新:必须先查询到记录,再更新,否则会更新所有记录
// 错误示例:没有指定记录,会更新所有行 db.Model(&User{}).Update("age", 20) // 正确示例:指定条件 db.Model(&User{}).Where("id = ?", 1).Update("age", 20) -
零值处理:
- 使用
struct更新时,零值字段会被忽略 - 使用
map更新时,零值字段会被更新
- 使用
-
钩子函数:更新操作会触发
BeforeUpdate和AfterUpdate钩子,可以在模型中定义:func (u *User) BeforeUpdate(tx *gorm.DB) error { // 更新前的逻辑 return nil } -
事务:复杂更新建议使用事务保证原子性:
db.Transaction(func(tx *gorm.DB) error { if err := tx.Model(&User{}).Where("id = 1").Update("age", 30).Error; err != nil { return err } if err := tx.Model(&User{}).Where("id = 2").Update("status", "VIP").Error; err != nil { return err } return nil // 提交事务 })
浙公网安备 33010602011771号