Gorm进阶 | 青训营笔记

98 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天

前言

大家好呀,这是我参加青训营伴学笔记创作活动的第 14 天,如存在问题,烦请各位斧正!

其中有一些关键图片超过了最大字符限制,不能上传了,我都使用特殊标记给它标记出来了,如有需要,请联系我。

查询接口

查询

1)检索单个对象

GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。

当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误。

result := db.First(&user)    // 获取第一条记录(主键升序),相当于
  ORDER BY id LIMIT 1;
    // result.RowsAffected:返回找到的记录数。   result.Error:returns error。
    db.Take(&user)   // 获取一条记录,没有指定排序字段,只相当于limit 1
    db.Last(&user)   // 获取最后一条记录(主键降序)

2)检索单个对象-指定Model:

同样的可以通过提供Model类型来使用,如果 model 类型没有定义主键,则按第一个字段排序。

result
  := map[string]interface{}{}
   
  DB.Model(&User{}).First(&result)

3)检索单个对象-指定Table:可以指定字段名来使用,这种方式不能用First,但能用Take:

result
  := map[string]interface{}{}
   
  DB.Table("users").Take(&result)
   
  4)获取全部记录:result := db.Find(&users)

条件查询WHERE

1)String条件

(1)db.Where("name = ?", "jinzhu").First(&user)

(2)IN:db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)

(3)LIKE:db.Where("name LIKE ?", "%jin%").Find(&users)

(4)AND:db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)

(5)TIME类型字段也可以传入符合日期格式的字符串:db.Where("updated_at > ?", lastWeek).Find(&users)

(6)BETWEEN:db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)

2)Struct & Map 条件

(1)Struct:db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)

(2)Map:db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)

(3)主键切片条件,相当于IN:db.Where([]int64{20, 21, 22}).Find(&users)

(4)结构体方式和Map下,只会查询非零值字段。这意味着如果您的字段值为 0、''、false 或其他 零值,该字段不会被用于构建查询条件

3)Not 条件

(1)构建NOT条件,用法与上面的Where用法相同,只不过查询结果是不符合传入条件的。

(2)比如下面的相当于WHERENOTname ="jinzhu":db.Not("name = ?", "jinzhu").First(&user)

4)OR条件

(1)db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)

(2)Struct或Map:db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)

选择特定字段

1)db.Select("name", "age").Find(&users)

2)db.Select([]string{"name", "age"}).Find(&users)

1)db.Order("age desc, name").Find(&users)

2)db.Order("age desc").Order("name").Find(&users)

Limit & Offset查询

Limit 指定获取记录的最大数量。 Offset 指定在开始返回记录之前要跳过的记录数量。

1)单独limit:db.Limit(3).Find(&users)

​2)单独offset:db.Offset(3).Find(&users) 相当于 SELECT * FROM users OFFSET 3;

3)两者结合:db.Limit(10).Offset(5).Find(&users) 相当于 SELECT * FROM users OFFSET 5 LIMIT 10;

4)通过 -1 消除 Offset 条件(Limit同理):

db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
   
  // SELECT * FROM users OFFSET 10; (users1)
   
  // SELECT * FROM users;
  (users2)

Distinct查询

db.Distinct("name", "age").Order("name, age desc").Find(&results)

智能选择字段

可以使用Select方法选择特定的字段,也可以定义一个较小的结构体,以实现调用 API 时自动选择特定的字段:

type User struct {
   
  ID    
  uint
   
  Name  
  string
   
  Age    int
   
  Gender string
  }
  type APIUser struct {
   
  ID  
  uint
   
  Name string
  }





// 查询时会自动选择 `id`, `name` 字段
  db.Model(&User{}).Limit(10).Find(&APIUser{})          // SELECT `id`, `name` FROM `users` LIMIT 10

子查询

子查询可以嵌套在查询中,比如:

db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)

// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

结果集处理

1)Rows.Next() 需要关闭

每次返回的rows 都是新的实例,占用一个数据库连接. rows.next()直到false 就会关闭,否则就会出现资源没有被回收,

但数据库设置的超时回收也会去回收 但是会降低服务Tps。

增删改接口

插入

1)插入一条数据

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()} ​ 
    result := db.Create(&user) // 通过数据的指针来创建​ 
    user.ID            
  // 返回插入数据的主键
    result.Error        // 返回 error
    result.RowsAffected // 返回插入记录的条数

2)批量插入

将切片数据传递给 Create 方法,GORM 将生成一个单一的 SQL 语句来插入所有数据,并回填主键的值,钩子方法也会被调用。

varusers = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
DB.Create(&users)

3)通过Map数据类型创建记录

GORM 支持根据 map[string]interface{} 和 []map[string]interface{}{} 单个/批量创建记录

DB.Model(&User{}).Create(map[string]interface{}{
   
  "Name": "jinzhu", "Age": 18,
  })
   
  DB.Model(&User{}).Create([]map[string]interface{}{
   
  {"Name": "jinzhu_1", "Age": 18},
   
  {"Name": "jinzhu_2", "Age": 20},
  })

删除

1)删除一条记录:

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

(1)db.Delete(&email) // email对象的id要有正确的值
(2)带额外条件的删除:db.Where("name = ?", "jinzhu").Delete(&email)

2)根据主键删除

(1)db.Delete(&User{}, 10)

(2)db.Delete(&User{}, "10")

(3)db.Delete(&users, []int{1,2,3})

3)批量删除

(1)db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})

(2)db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")

4)返回删除行的数据

(1)返回所有列

var
  users []User   // 用来接收的
   
  DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)

(2)返回指定的列

DB.Clauses(clause.Returning{Columns:
  []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users)
   
  // DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary`

5)软删除

(1)如果模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!

(2)拥有软删除能力的模型调用 Delete 时,记录不会从数据库中被真正删除。

但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。

(3)查询被软删除的记录, 可以使用 Unscoped 找到被软删除的记录:

db.Unscoped().Where("age = 20").Find(&users)

修改

1)保存所有字段

Save 会保存所有的字段,即使字段是零值:db.Save(&user)

2)更新单列(如一行记录的某个字段)

(1)当使用 Update 更新单列时,需要有一些条件,否则将会引起错误 ErrMissingWhereClause 。

(2)当使用 Model 方法,并且值中有主键值时,主键将会被用于构建条件。

(3)条件更新:db.Model(&User{}).Where("active = ?", true).Update("name", "hello")

(4)根据传入model中的主键值更新:db.Model(&user).Update("name", "hello") // 传入的user 的 ID 是 xxx

(5)根据条件和 model 的值进行更新:

3)更新多列

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

(1)根据struct:db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})

(2)根据map:db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})

4)更新选定字段

如果想要在更新时选定、忽略某些字段,您可以使用 Select、Omit

(1)选定字段

 Select:db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
    // 语句为 UPDATE users SET name='hello' WHERE id=111;

(2)忽略字段 Omit:db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})

(3)两者可以一起使用。

5)批量更新

如果尚未通过 Model 指定记录的主键,则 GORM 会执行批量更新,可以根据struct或map批量更新。

事务


禁用默认事务

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

1)全局禁用

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
   
  SkipDefaultTransaction: true,
  })

2)持续会话模式

tx := db.Session(&Session{SkipDefaultTransaction: true})
  tx.First(&user, 1)
  tx.Find(&users)
  tx.Model(&user).Update("Age", 18)

事务开启

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

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

})

return nil

})

手动事务

Gorm 支持直接调用事务控制方法(commit、rollback)

// 开始事务

tx := db.Begin()

// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')

tx.Create(...)

// 遇到错误时回滚事务

tx.Rollback()

// 否则,提交事务

tx.Commit()