导航

gorm实现mysql增删查改

Posted on 2025-06-03 14:42  Zyeah  阅读(131)  评论(0)    收藏  举报

目标: golang gorm 操作本地数据库的增删改查
其中gorm全程是golang object relations mapping
参考文章

这个全一点:https://blog.csdn.net/m0_66100833/article/details/132920354
这个短一点:https://juejin.cn/post/7262554603488837689

一、连接并打开数据库

  1. 相关的包
gorm.io/gorm
gorm.io/driver/mysql
  1. 连接数据库
	// 定义数据库连接字符串
	//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标签的字段 索引
结构体实例 记录(行)
  1. 通过标签修改字段名和字段数据类型
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)

注意事项

  1. 零值问题:结构体条件查询会忽略零值字段(如0""false),改用map条件可避免
  2. 软删除:使用gorm.Model的模型默认支持软删除,已删除记录不会被查询
  3. 错误处理:查询结果需检查错误,如db.First(&user).Error
  4. 性能优化:复杂查询建议使用原生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】、注意事项

  1. 软删除的查询限制
    常规查询(如 Find/First)会自动过滤掉 DeletedAt 非空的记录,如需查询软删除记录,需使用 Unscoped

    // 查询所有记录(包括软删除)
    var users []User
    db.Unscoped().Find(&users)
    
  2. 硬删除的危险性
    硬删除会永久删除数据,执行前务必确认条件正确,建议先通过 Where 条件验证数据范围。

  3. 钩子函数与事务
    删除操作会触发 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】、注意事项

  1. 先查询后更新:必须先查询到记录,再更新,否则会更新所有记录

    // 错误示例:没有指定记录,会更新所有行
    db.Model(&User{}).Update("age", 20)
    
    // 正确示例:指定条件
    db.Model(&User{}).Where("id = ?", 1).Update("age", 20)
    
  2. 零值处理

    • 使用 struct 更新时,零值字段会被忽略
    • 使用 map 更新时,零值字段会被更新
  3. 钩子函数:更新操作会触发 BeforeUpdateAfterUpdate 钩子,可以在模型中定义:

    func (u *User) BeforeUpdate(tx *gorm.DB) error {
        // 更新前的逻辑
        return nil
    }
    
  4. 事务:复杂更新建议使用事务保证原子性:

    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 // 提交事务
    })