Gorm使用笔记 | 豆包MarsCode AI刷题

104 阅读8分钟

特性

  • 全功能 ORM
  • 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
  • Create,Save,Update,Delete,Find 中钩子方法
  • 支持 Preload、Joins的预加载
  • 事务,嵌套事务,Save Point,Rollback To Saved Point
  • Context、预编译模式、DryRun 模式
  • 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
  • SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
  • 复合主键,索引,约束
  • Auto Migration
  • 自定义 Logger
  • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
  • 每个特性都经过了测试的重重考验
  • 开发者友好

创建/增加

模型构建

type User struct {
	ID           uint           // Standard field for the primary key
	Name         string         // 一个常规字符串字段
	Email        *string        // 一个指向字符串的指针, allowing for null values
	Age          uint8          // 一个未签名的8位整数
	Birthday     time.Time      // A pointer to time.Time, can be null
	MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings
	ActivatedAt  sql.NullTime   // Uses sql.NullTime for nullable time fields
	CreatedAt    time.Time      // 创建时间(由GORM自动管理)
	UpdatedAt    time.Time      // 最后一次更新时间(由GORM自动管理)
}

创建逻辑

func main() {
	dsn := "root:123123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		fmt.Println("open runoob failed")
	}
        
        db.AutoMigrate(&User{})
	// AutoMigrate 用于自动迁移您的 schema,保持您的 schema 是最新的
	// AutoMigrate 会创建表、缺失的外键、约束、列和索引。 It will change existing column’s type if its size, precision changed, or if it’s changing from non-nullable to nullable. 出于保护您数据的目的,它 不会 删除未使用的列

	user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
	result := db.Create(&user)
	if result.Error != nil {
		panic(result.Error)
	}
	fmt.Println(result.RowsAffected)

	// 创建多条记录
	users := []*User{
		{Name: "Master", Age: 18, Birthday: time.Now()},
		{Name: "Jackson", Age: 19, Birthday: time.Now()},
	}

	// 指定字段创建记录
	result = db.Select("Name", "Age", "CreatedAt").Create(users)
	if result.Error != nil {
		panic(result.Error)
	}
	fmt.Println(result.RowsAffected)
}

运行效果

执行前

image.png

执行后

image.png

image.png

image.png

查询

    // 获取第一条记录(主键升序)
    u := &User{}
    db.First(u)
    // SELECT * FROM users ORDER BY id LIMIT 1;

    // 获取一条记录,没有指定排序字段
    db.Take(u)
    // SELECT * FROM users LIMIT 1;

    // 获取最后一条记录(主键降序)
    db.Last(u)
    // SELECT * FROM users ORDER BY id DESC LIMIT 1;

Tips: 关于如何打印查询结果,GORM 查询结果通常是结构体的切片或单个结构体实例,使用fmt.Printf()方法对查询结果进行打印。例如:

    u := &User{}
    result := db.First(u)
    errors.Is(result.Error, gorm.ErrRecordNotFound)
    fmt.Printf("%+v\n", u)

查询结果

image.png

FirstLast 方法会按主键排序找到第一条记录和最后一条记录 (分别)。
只有在目标 struct 是指针或者通过 db.Model() 指定 model 时,该方法才有效。 此外,如果相关 model 没有定义主键,那么将按 model 的第一个字段进行排序。 例如:

    var user User
    var users []User

    // works because destination struct is passed in
    db.First(&user)
    // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

    // works because model is specified using `db.Model()`
    result := map[string]interface{}{}
    db.Model(&User{}).First(&result)
    // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

    // doesn't work
    result := map[string]interface{}{}
    db.Table("users").First(&result)

    // works with Take
    result := map[string]interface{}{}
    db.Table("users").Take(&result)

查询结果

image.png 如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user)Find方法可以接受struct和slice的数据。

根据主键索引查询

    db.First(&user, 2)
    // SELECT * FROM users WHERE id = 2;

    db.First(&user, "2")
    // SELECT * FROM users WHERE id = 2;

    db.Find(&users, []int{1,2,3})
    // SELECT * FROM users WHERE id IN (1,2,3);

查询结果

image.png

条件

使用Where方法,第一个参数是对应的SQL语句,之后的参数都是SQL语句对应的参数。

String条件

db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

image.png

// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

image.png

db.Where("updated_at > ?", lastWeek).Find(&users)  
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';  
  
// BETWEEN  
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)  
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

image.png

Struct&Map条件


// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;

// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

查询结果

image.png

Tips: 使用结构体作为查询条件时,GORM只会查询非零值字段,如果该字段为0值时给字段不会被用于构建查询条件,应使用map来构建查询条件。

更新

db.First(&user)  
  
user.Name = "jinzhu 2"  
user.Age = 100  
db.Save(&user)  
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

image.png Save 是一个组合函数。 如果保存值不包含主键,它将执行 Create,否则它将执行 Update (包含所有字段)。 不要将 Save 和 Model一同使用, 这是 未定义的行为

更新单列

当使用 Update 更新单列时,需要有一些条件,否则将会引起ErrMissingWhereClause 错误。 当使用 Model 方法,并且它有主键值时,主键将会被用于构建条件,例如:

// 根据条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

更新多列

Updates 方法支持 struct 和 map[string]interface{} 参数。当使用 struct 更新时,默认情况下GORM 只会更新非零值的字段

// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

注意 使用 struct 更新时, GORM 将只更新非零值字段。 你可能想用 map 来更新属性,或者使用 Select 声明字段来更新

删除

删除一条记录时,删除对象需要指定主键,否则会触发批量删除,例如:

// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字

db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;

db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;

db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

批量删除

如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";

db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";

可以将一个主键切片传递给Delete 方法,以便更高效的删除数据量大的记录

var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
db.Delete(&users)
// DELETE FROM users WHERE id IN (1,2,3);

db.Delete(&users, "name LIKE ?", "%jinzhu%")
// DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3);

软删除

如果你的模型包含了 gorm.DeletedAt字段(该字段也被包含在gorm.Model中),那么该模型将会自动获得软删除的能力!

当调用Delete时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt设置为当前时间,而后的一般查询方法将无法查找到此条记录。

type User struct {   
    ID      int   
    Deleted gorm.DeletedAt   
    Name    string 
}

查找被软删除的记录

你可以使用Unscoped来查询到被软删除的记录

db.Unscoped().Where("age = 20").Find(&users) 
// SELECT * FROM users WHERE age = 20; 

永久删除

你可以使用 Unscoped来永久删除匹配的记录

db.Unscoped().Delete(&order) 
// DELETE FROM orders WHERE id=10; 

事务

要在事务中执行一系列操作,一般流程如下:

db.Transaction(func(tx *gorm.DB) error {
  // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回任何错误都会回滚事务
    return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
  }

  // 返回 nil 提交事务
  return nil
})

嵌套事务

GORM 支持嵌套事务,可以回滚较大事务内执行的一部分操作,例如:

db.Transaction(func(tx *gorm.DB) error {
  tx.Create(&user1)

  tx.Transaction(func(tx2 *gorm.DB) error {
    tx2.Create(&user2)
    return errors.New("rollback user2") // Rollback user2
  })

  tx.Transaction(func(tx3 *gorm.DB) error {
    tx3.Create(&user3)
    return nil
  })

  return nil
})

// Commit user1, user3

SavePoint、RollbackTo

GORM 提供了 SavePointRollbackto 方法,来提供保存点以及回滚至保存点功能,例如:

tx := db.Begin() 
tx.Create(&user1)  
tx.SavePoint("sp1") tx.Create(&user2) 
tx.RollbackTo("sp1") 
// Rollback user2  
tx.Commit() 
// Commit user1

为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约 30%+ 性能提升。

// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  SkipDefaultTransaction: true,
})

// 持续会话模式
tx := db.Session(&Session{SkipDefaultTransaction: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)

Hook

对象生命周期

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。

如果您已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。

钩子方法的函数签名应该是 func(*gorm.DB) error

创建对象

创建时可用的 hook

// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务

示例

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {   
    u.UUID = uuid.New()    
    if !u.IsValid() {     
        err = errors.New("can't save invalid data")
    }   
    return 
}

func (u *User) AfterCreate(tx *gorm.DB) (err error) {   
    if u.ID == 1 {     
        tx.Model(u).Update("role", "admin")   
    }   
return 
}

注意 在 GORM 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果您的钩子返回了任何错误,则修改将被回滚。