gin框架学习-GORM框架进阶之CRUD接口(数据库增删改查操作)

简介: First、Last 方法会根据主键查找到第一个、最后一个记录, 它仅在通过结构体 struct 或提供 model 值进行查询时才起作用。

前言


感谢开源项目gin-vue-admin,以及1010工作室的视频教程

本人学识尚浅,如有错误,请评论指出,谢谢!

详细可见个人博客:https://linzyblog.netlify.app/


一、创建


GORM里的创建(Create方法),也就是数据库插入语句(Insert语句),可以创建单条或者多条,指定字段创建等


1、Create方法


1)创建单条记录


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


2)创建多条记录


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


  var users = []User{{Name: "linzy1"}, {Name: "linzy2"}, {Name: "linzy3"}}
  db.Create(&users)
  for _, user := range users {
    user.ID // 1,2,3
  }


3)指定字段创建记录


  • Select 方法指定需要创建的字段


  db.Select("Name", "Age", "CreatedAt").Create(&user)
  // INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("linzy", 23, "2022-07-08 11:05:21.775")


  • 用 Omit 方法会更新未给出的字段创建记录。


  db.Omit("Name", "Age", "CreatedAt").Create(&user)
  // INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2022-07-04 00:00:00.000", "2022-07-08 11:05:21.775")


2、CreateInBatches


使用 CreateInBatches 创建时,你还可以指定创建的数量,例如:


  var 用户 = []User{name: "linzy_1"}, ...., {Name: "linzy_10000"}}
// 数量为 100
  db.CreateInBatches(用户, 100)


Upsert 和 Create With Associations 也支持批量插入


3、创建钩子


GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法


例如:


  func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
    u.UUID = uuid.New()
      if u.Role == "admin" {
          return errors.New("invalid role")
      }
      return
  }


如果您想跳过 钩子 方法,您可以使用 SkipHooks 会话模式,例如:


  //SkipHooks为true表示当前方法 不执行钩子方法
  DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
  DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)
  DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)


4、根据 Map 创建


通常我们用GORM都是用结构体 struct 来创建记录,而GORM 也支持根据 map[string]interface{} []map[string]interface{}{} 创建记录,例如:


  //创建单条记录
  db.Model(&User{}).Create(map[string]interface{}{
    "Name": "linzy", "Age": 23,
  })
  //创建多条记录
  db.Model(&User{}).Create([]map[string]interface{}{
    {"Name": "linzy_1", "Age": 23},
    {"Name": "linzy_2", "Age": 66},
    {"Name": "linzy_3", "Age": 88},
  })


注意:根据 map 创建记录时,association (查找关联方法)不会被调用,且主键也不会自动填充


5、高级选项


1)关联创建


创建关联数据时,如果关联值是非零值,这些关联会被 upsert,且它们的 Hook 钩子方法也会被调用


  type CreditCard struct {
    gorm.Model
    Number string
    UserID uint
  }
  type User struct {
    gorm.Model
    Name       string
    CreditCard CreditCard // 一对一的关系
  }
  db.Create(&User{
    Name:       "linzy",
    CreditCard: CreditCard{Number: "123456789"},
  })
  // INSERT INTO `users` ...
  // INSERT INTO `credit_cards` ...


您也可以通过 Select、 Omit 跳过关联保存,例如:


  db.Omit("CreditCard").Create(&user)
  // 跳过所有关联
  db.Omit(clause.Associations).Create(&user)


2)默认值


您可以通过标签 default 为字段定义默认值,如:


插入记录到数据库时,默认值 会被用于 填充值为 零值 的字段


type User struct {
  ID   int64
  Name string `gorm:"default:linzy"`
  Age  int64  `gorm:"default:23"`
}


注意: 像 0、‘’、false 等零值,不会将这些字段定义的默认值保存到数据库。您需要使用指针类型或 Scanner/Valuer 来避免这个问题,例如:


type User struct {
  gorm.Model
  Name   string
  Age    *int         `gorm:"default:23"`
  Active sql.NullBool `gorm:"default:true"`
}


注意: 若要数据库有默认、虚拟 / 生成的值,你必须为字段设置 default 标签。若要在迁移时跳过默认值定义,你可以使用 default:(-),例如:


type User struct {
  ID        string `gorm:"default:uuid_generate_v3()"` // 数据库函数
  FirstName string
  LastName  string
  Age       uint8
  FullName  string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);`
}


3)设置了软删除,解决插入冲突问题 error": “Error 1062: Duplicate entry ‘xxx’ for key ‘xxx.xxx’”


我们如果设置了 deleted_at 字段也就是软删除,如果我们数据存在一条已经被软删除的数据,插入相同的数据会出现插入冲突问题。


GORM支持MySQL的 insert into…on duplicate key update 语句,插入冲突时更新记录,支持批量。


// 在冲突时,更新除主键以外的所有列到新值。
db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&table)


除次以外,GORM还提供冲突部分更新、自定义冲突字段的功能。


// 在`id`冲突时,将列更新为新值
db.Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "id"}},
  // 指定更新的字段
  DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)
// 在`id`冲突时,将列更新为默认值
db.Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "id"}},
  DoUpdates: clause.Assignments(map[string]interface{}{"role": "user"}),
}).Create(&users)


二、查询


GORM里的查询(Find方法),也就是数据库查询语句(Select语句),可以查询所有数据、查询指定条件数据,查询首条数据,查询最后一条数据


1、查询单条数据


GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误


  // 获取第一条记录(主键升序)
  db.First(&user)
  // SELECT * FROM users ORDER BY id LIMIT 1;
  // 获取一条记录,没有指定排序字段
  db.Take(&user)
  // SELECT * FROM users LIMIT 1;
  // 获取最后一条记录(主键降序)
  db.Last(&user)
  // SELECT * FROM users ORDER BY id DESC LIMIT 1;
  result := db.First(&user)
  result.RowsAffected // 返回找到的记录数
  result.Error        // returns error
  // 检查 ErrRecordNotFound 错误
  errors.Is(result.Error, gorm.ErrRecordNotFound)


  • First、Last 方法会根据主键查找到第一个、最后一个记录, 它仅在通过结构体 struct 或提供 model 值进行查询时才起作用。 如果 model 类型没有定义主键,则按第一个字段排序,例如:


  var user User
  // 可以
  db.First(&user)
  // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
  // 可以
  result := map[string]interface{}{}
  db.Model(&User{}).First(&result)
  // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
  // 不行
  result := map[string]interface{}{}
  db.Table("users").First(&result)
  // 但可以配合 Take 使用
  result := map[string]interface{}{}
  db.Table("users").Take(&result)
  // 根据第一个字段排序
  type Language struct {
    Code string
    Name string
  }
  db.First(&Language{})
  // SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1


2、根据主键查询


您可以使用 内联条件 来检索对象。 传入字符串参数时注意避免 SQL 注入问题


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


3、查询全部数据


  // 获取全部记录
  result := db.Find(&users)
  // SELECT * FROM users;
  result.RowsAffected // 返回找到的记录数,相当于 `len(users)`
  result.Error        // returns error


4、条件查询


条件查询即查询满足条件的所有数据,GORM框架给用户提供了 String条件 、Struct & Map 条件、内联条件 、Not 条件以及 Or 条件查询方式


1)String条件


通过Where方法,对String里的?进行填充,来完成条件查询


  // 获取第一条匹配的记录
  db.Where("name = ?", "linzy").First(&user)
  // SELECT * FROM users WHERE name = 'linzy' ORDER BY id LIMIT 1;
  // 获取全部匹配的记录
  db.Where("name <> ?", "linzy").Find(&users)
  // SELECT * FROM users WHERE name <> 'linzy';
  // IN
  db.Where("name IN ?", []string{"linzy", "linzy2"}).Find(&users)
  // SELECT * FROM users WHERE name IN ('linzy','linzy2');
  // LIKE
  db.Where("name LIKE ?", "%in%").Find(&users)
  // SELECT * FROM users WHERE name LIKE '%in%';
  // AND
  db.Where("name = ? AND age >= ?", "linzy", "22").Find(&users)
  // SELECT * FROM users WHERE name = 'linzy' AND age >= 22;
  // Time
  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';


2)Struct & Map 条件


  // Struct
  db.Where(&User{Name: "linzy", Age: 20}).First(&user)
  // SELECT * FROM users WHERE name = "linzy" AND age = 20 ORDER BY id LIMIT 1;
  // Map
  db.Where(map[string]interface{}{"name": "linzy", "age": 20}).Find(&users)
  // SELECT * FROM users WHERE name = "linzy" AND age = 20;
  // 主键切片条件
  db.Where([]int64{20, 21, 22}).Find(&users)
  // SELECT * FROM users WHERE id IN (20, 21, 22);


注意: 当使用结构作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为 0、‘’、false 或其他 零值,该字段不会被用于构建查询条件,例如:


  db.Where(&User{Name: "linzy", Age: 0}).Find(&users)
  // SELECT * FROM users WHERE name = "linzy";


您可以使用 map 来构建查询条件,例如:


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


3)内联条件


用法跟 Where 方法一样


  // SELECT * FROM users WHERE id = 23;
  // 根据主键获取记录,如果是非整型主键
  db.First(&user, "id = ?", "string_primary_key")
  // SELECT * FROM users WHERE id = 'string_primary_key';
  // Plain SQL
  db.Find(&user, "name = ?", "linzy")
  // SELECT * FROM users WHERE name = "linzy";
  db.Find(&users, "name <> ? AND age > ?", "linzy", 20)
  // SELECT * FROM users WHERE name <> "linzy" AND age > 20;
  // Struct
  db.Find(&users, User{Age: 20})
  // SELECT * FROM users WHERE age = 20;
  // Map
  db.Find(&users, map[string]interface{}{"age": 20})
  // SELECT * FROM users WHERE age = 20;


4)Not 条件


Not在sql语句中是一个逻辑运算符,取反的用处,真为假,假为真,语句用法跟 Where 方法一样


  db.Not("name = ?", "linzy").First(&user)
  // SELECT * FROM users WHERE NOT name = "linzy" ORDER BY id LIMIT 1;
  // Not In
  db.Not(map[string]interface{}{"name": []string{"linzy", "linzy 2"}}).Find(&users)
  // SELECT * FROM users WHERE name NOT IN ("linzy", "linzy 2");
  // Struct
  db.Not(User{Name: "linzy", Age: 18}).First(&user)
  // SELECT * FROM users WHERE name <> "linzy" AND age <> 18 ORDER BY id LIMIT 1;
  // 不在主键切片中的记录
  db.Not([]int64{1, 2, 3}).First(&user)
  // SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;


5)Or条件


Where 方法和 内联条件 存在多个条件的时候都是用AND联系,表示条件都必须满足的数据,那我们如果只需要满足其中一种条件呢,那就需要 Or条件 了,语句用法跟 Where 方法一样


  db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
  // SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
  // Struct
  db.Where("name = 'linzy'").Or(User{Name: "linzy 2", Age: 18}).Find(&users)
  // SELECT * FROM users WHERE name = 'linzy' OR (name = 'linzy 2' AND age = 18);
  // Map
  db.Where("name = 'linzy'").Or(map[string]interface{}{"name": "linzy 2", "age": 18}).Find(&users)
  // SELECT * FROM users WHERE name = 'linzy' OR (name = 'linzy 2' AND age = 18);


5、选择特定字段


1)Select


选择您想从数据库中检索的字段,默认情况下会选择全部字段


  db.Select("name", "age").Find(&users)
  // SELECT name, age FROM users;
  db.Select([]string{"name", "age"}).Find(&users)
  // SELECT name, age FROM users;
  db.Table("users").Select("COALESCE(age,?)", 42).Rows()
  // SELECT COALESCE(age,'42') FROM users;


2)结构体智能选择字段


GORM 允许通过 Select 方法选择特定的字段,如果在应用程序中经常使用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


6、Order排序


指定从数据库检索记录时的排序方式


  db.Order("age desc, name").Find(&users)
  // SELECT * FROM users ORDER BY age desc, name;
  // 多个 order
  db.Order("age desc").Order("name").Find(&users)
  // SELECT * FROM users ORDER BY age desc, name;
  db.Clauses(clause.OrderBy{
    Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
  }).Find(&User{})
  // SELECT * FROM users ORDER BY FIELD(id,1,2,3)


7、Limit & Offset


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


  db.Limit(3).Find(&users)
  // SELECT * FROM users LIMIT 3;
  // 通过 -1 消除 Limit 条件
  db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
  // SELECT * FROM users LIMIT 10; (users1)
  // SELECT * FROM users; (users2)
  db.Offset(3).Find(&users)
  // SELECT * FROM users OFFSET 3;
  db.Limit(10).Offset(5).Find(&users)
  // SELECT * FROM users OFFSET 5 LIMIT 10;
  // 通过 -1 消除 Offset 条件
  db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
  // SELECT * FROM users OFFSET 10; (users1)
  // SELECT * FROM users; (users2)


8、Group & Having


  • Group 指定字段进行分组
  • Having 指定字段分组后的条件查询


type result struct {
  Date  time.Time
  Total int
}
  db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
  // SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name`
  db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
  // SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
  rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
  for rows.Next() {
    ...
   }
  rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
  for rows.Next() {
    ...
  }
type Result struct {
  Date  time.Time
  Total int64
}
  db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)


9、Distinct去重


从模型中选择不相同的值,简而言之,把选定的字段里重复数据去掉,只留下一条


Distinct 也可以配合 Pluck, Count 使用


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


10、Joins连接


数据空的连接操作,有左连接(left join),右连接(right join),全连接(outer join)、内连接(inner join),指定 Joins 条件


type result struct {
  Name  string
  Email string
}
func main() {
  //左连接
  db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
  // SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
  rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
  for rows.Next() {
    ...
  }
  db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
  // 带参数的多表连接
  db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "linzy@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
}


11、Joins 预加载


您可以使用 Joins 实现单条 SQL 预加载关联记录,例如:


  db.Joins("Company").Find(&users)
  // SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;


12、Scan


Scan 结果存储于结构体 struct,用法与 Find 类似


type Result struct {
  Name string
  Age  int
}
func main() {
  var result Result
  db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
  // 原生 SQL
  db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
}


三、更新


GORM里的更新(Update方法),也就是数据库更新语句(Update语句),可以更新单列、更新多列、更新选定列


1、更新单列


当使用 Update 更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause 错误。当使用了 Model 方法,且该对象主键有值,该值会被用于构建条件,例如:


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


2、更新多列


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


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


注意:当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作


3、更新选定字段


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


  // Select 和 Map
  // User's ID is `111`:
  db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "linzy", "age": 18, "actived": false})
  // UPDATE users SET name='linzy' WHERE id=111;
  db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "linzy", "age": 18, "actived": false})
  // UPDATE users SET age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
  // Select 和 Struct (可以选中更新零值字段)
  db.Model(&result).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
  // UPDATE users SET name='new_name', age=0 WHERE id=111;


4、保存所有字段


Save 会保存所有的字段,即使字段是零值


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


5、更新 Hook


对于更新操作,GORM 支持 BeforeSave、BeforeUpdate、AfterSave、AfterUpdate 钩子,这些方法将在更新记录时被调用


例如:


func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
  if u.Role == "admin" {
    return errors.New("admin user not allowed to update")
  }
  return
}


6、批量更新


如果您尚未通过 Model 指定记录的主键,则 GORM 会执行批量更新


  // 根据 struct 更新
  db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "linzy", Age: 18})
  // UPDATE users SET name='linzy', age=18 WHERE role = 'admin;
  // 根据 map 更新
  db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "linzy", "age": 18})
  // UPDATE users SET name='linzy', age=18 WHERE id IN (10, 11);


1)阻止全局更新


如果在没有任何条件的情况下执行批量更新,默认情况下,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误


对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:


  db.Model(&User{}).Update("name", "linzy").Error // gorm.ErrMissingWhereClause
  db.Model(&User{}).Where("1 = 1").Update("name", "linzy")
  // UPDATE users SET `name` = "linzy" WHERE 1=1
  db.Exec("UPDATE users SET name = ?", "linzy")
  // UPDATE users SET name = "linzy"
  db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "linzy")
  // UPDATE users SET `name` = "linzy"


2)更新的记录数


获取受更新影响的行数


  // 通过 `RowsAffected` 得到更新的记录数
  result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
  // UPDATE users SET name='hello', age=18 WHERE role = 'admin;
  result.RowsAffected // 更新的记录数
  result.Error        // 更新的错误


7、高级选项


1)使用 SQL 表达式更新


GORM 允许使用 SQL 表达式更新列,例如:


  // product 的 ID 是 `3`
  db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
  // UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
  db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
  // UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
  db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
  // UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;
  db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
  // UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;


2)根据子查询进行更新


使用子查询更新表


  db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
  // UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);
  db.Table("users as u").Where("name = ?", "linzy").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
  db.Table("users as u").Where("name = ?", "linzy").Updates(map[string]interface{}{}{"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})


3)不使用 Hook 和时间追踪


如果您想在更新时跳过 Hook 方法且不追踪更新时间,可以使用 UpdateColumn、UpdateColumns,其用法类似于 Update、Updates

  // 更新单个列
  db.Model(&user).UpdateColumn("name", "hello")
  // UPDATE users SET name='hello' WHERE id = 111;
  // 更新多个列
  db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
  // UPDATE users SET name='hello', age=18 WHERE id = 111;
  // 更新选中的列
  db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
  // UPDATE users SET name='hello', age=0 WHERE id = 111;


四、删除


GORM里的更新(Delete方法),也就是数据库删除语句(Delete语句),可以删除单条记录,删除多条记录,根据主键删除,删除Hook钩子


1、删除单条记录


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


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


2、删除多条记录


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


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


1)阻止全局删除


如果在没有任何条件的情况下执行批量删除,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误


对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:


  db.Delete(&User{}).Error // gorm.ErrMissingWhereClause
  db.Where("1 = 1").Delete(&User{})
  // DELETE FROM `users` WHERE 1=1
  db.Exec("DELETE FROM users")
  // DELETE FROM users
  db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
  // DELETE FROM users


3、根据主键删除


GORM 允许通过内联条件指定主键来检索对象,但只支持整型数值,因为 string 可能导致 SQL 注入。


  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);


4、删除Hook钩子


对于删除操作,GORM 支持 BeforeDelete、AfterDelete Hook,在删除记录时会调用这些方法


func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to delete")
    }
    return
}


5、*软删除


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


拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过正常的查询方法找到该记录。


  // user 的 ID 是 `111`
  db.Delete(&user)
  // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
  // 批量删除
  db.Where("age = ?", 20).Delete(&User{})
  // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
  // 在查询时会忽略被软删除的记录
  db.Where("age = 20").Find(&user)
  // SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;


如果您不想引入 gorm.Model,您也可以这样启用软删除特性:


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


1)查找被软删除的记录


您可以使用 Unscoped 找到被软删除的记录


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


2)永久删除


您也可以使用 Unscoped 永久删除匹配的记录


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


五、SQL 构建器


1、原生 SQL


  • 原生查询 SQL 和 Scan


type Result struct {
  ID   int
  Name string
  Age  int
}
func main() {
  var result Result
  db.Raw("SELECT id, name, age FROM users WHERE id = ?", 3).Scan(&result)
  var age int
  db.Raw("select sum(age) from users where role = ?", "admin").Scan(&age)
}


  • Exec 原生 SQL


  db.Exec("DROP TABLE users")
  db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
  // Exec SQL 表达式
  db.Exec("update users set money=? where name = ?", gorm.Expr("money * ? + ?", 10000, 1), "linzy")


注意 GORM 允许缓存预编译 SQL 语句来提高性能


2、命名参数


GORM 支持 sql.NamedArg、map[string]interface{}{} struct 形式的命名参数,例如:


  db.Where("name1 = @name OR name2 = @name", sql.Named("name", "linzy")).Find(&user)
  // SELECT * FROM `users` WHERE name1 = "linzy" OR name2 = "linzy"
  db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "linzy2"}).First(&result3)
  // SELECT * FROM `users` WHERE name1 = "linzy2" OR name2 = "linzy2" ORDER BY `users`.`id` LIMIT 1
  // 原生 SQL 及命名参数
  db.Raw("SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name",
    sql.Named("name", "linzy1"), sql.Named("name2", "linzy2")).Find(&user)
  // SELECT * FROM users WHERE name1 = "linzy1" OR name2 = "linzy2" OR name3 = "linzy1"
  db.Exec("UPDATE users SET name1 = @name, name2 = @name2, name3 = @name",
    sql.Named("name", "linzynew"), sql.Named("name2", "linzynew2"))
  // UPDATE users SET name1 = "linzynew", name2 = "linzynew2", name3 = "linzynew"
  db.Raw("SELECT * FROM users WHERE (name1 = @name AND name3 = @name) AND name2 = @name2",
    map[string]interface{}{"name": "linzy", "name2": "linzy2"}).Find(&user)
  // SELECT * FROM users WHERE (name1 = "linzy" AND name3 = "linzy") AND name2 = "linzy2"
  type NamedArgument struct {
    Name  string
    Name2 string
  }
  db.Raw("SELECT * FROM users WHERE (name1 = @Name AND name3 = @Name) AND name2 = @Name2",
    NamedArgument{Name: "linzy", Name2: "linzy2"}).Find(&user)
  // SELECT * FROM users WHERE (name1 = "linzy" AND name3 = "linzy") AND name2 = "linzy2"


3、DryRun 模式


在不执行的情况下生成 SQL ,可以用于准备或测试生成的 SQL


  stmt := db.Session(&Session{DryRun: true}).First(&user, 1).Statement
  stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
  stmt.Vars         //=> []interface{}{1}


  • Row & Rows


获取 *sql.Row 结果


  // 使用 GORM API 构建 SQL
  row := db.Table("users").Where("name = ?", "linzy").Select("name", "age").Row()
  row.Scan(&name, &age)
  // 使用原生 SQL
  row := db.Raw("select name, age, email from users where name = ?", "linzy").Row()
  row.Scan(&name, &age, &email)


  • 获取 *sql.Rows 结果


  // 使用 GORM API 构建 SQL
  rows, err := db.Model(&User{}).Where("name = ?", "linzy").Select("name, age, email").Rows()
  defer rows.Close()
  for rows.Next() {
    rows.Scan(&name, &age, &email)
    // 业务逻辑...
  }
  // 原生 SQL
  rows, err := db.Raw("select name, age, email from users where name = ?", "linzy").Rows()
  defer rows.Close()
  for rows.Next() {
    rows.Scan(&name, &age, &email)
    // 业务逻辑...
  }


转到 FindInBatches 获取如何在批量中查询和处理记录的信息, 转到 Group 条件 获取如何构建复杂 SQL 查询的信息


4、将 sql.Rows 扫描至 model


使用 ScanRows 将一行记录扫描至 struct,例如:


  rows, err := db.Model(&User{}).Where("name = ?", "linzy").Select("name, age, email").Rows() // (*sql.Rows, error)
  defer rows.Close()
  var user User
  for rows.Next() {
    // ScanRows 将一行扫描至 user
    db.ScanRows(rows, &user)
    // 业务逻辑...
  }


参考GORM中文文档:https://learnku.com/docs/gorm/v2/connecting_to_the_database/9731#64cea3

目录
相关文章
|
11天前
|
SQL Java 数据库连接
数据库常用接口
ODBC(Open Database Connectivity):开放数据库互连技术为访问不同的SQL数据库提供了一个共同的接口。ODBC使用SQL作为访问数据的标准。这一接口提供了最大限度的互操作性,一个应用程序可以通过共同的一组代码访问不同的SQL数据库管理系统(DBMS)。 一个基于ODBC的应用程序对数据库的操作不依赖任何DBMS,不直接与DBMS打交道,所有的数据库操作由对应的DBMS的ODBC驱动程序完成。也就是说,不论是Access,MySQL还是Oracle数据库,均可用ODBC API进行访问。由此可见,ODBC的最大优点是能以统一的方式处理所有的数据库。
|
30天前
|
SQL JavaScript 关系型数据库
node博客小项目:接口开发、连接mysql数据库
【10月更文挑战第14天】node博客小项目:接口开发、连接mysql数据库
|
1月前
|
数据可视化 API PHP
学生信息管理系统-可视化-科目管理CRUD代码生成器
学生信息管理系统-可视化-科目管理CRUD代码生成器
40 5
|
2月前
|
前端开发 IDE 数据库连接
ThinkPHP6 模型层的模型属性,表映射关系,以及如何在控制层中使用模型层和模型层中的简单CRUD
本文详细介绍了ThinkPHP6中模型层的使用,包括模型属性设置、表映射关系、以及如何在控制层中使用模型层进行CRUD操作。
ThinkPHP6 模型层的模型属性,表映射关系,以及如何在控制层中使用模型层和模型层中的简单CRUD
|
3月前
|
SQL Java 数据库连接
Hibernate 是一款开源 ORM(对象关系映射)框架,封装了 JDBC,允许以面向对象的方式操作数据库,简化了数据访问层的开发。
Hibernate 是一款开源 ORM(对象关系映射)框架,封装了 JDBC,允许以面向对象的方式操作数据库,简化了数据访问层的开发。通过映射机制,它可以自动处理对象与数据库表之间的转换,支持主流数据库,提高了代码的可移植性和可维护性。其核心接口包括 SessionFactory、Session 和 Transaction 等,通过它们可以执行数据库的 CRUD 操作。配置方面,需在项目中引入 Hibernate 及数据库驱动依赖,并创建 `hibernate.cfg.xml` 配置文件来设置数据库连接和 Hibernate 行为参数。
47 1
|
3月前
|
SQL 数据库连接 API
ThinkPHP6实现增删改查接口
ThinkPHP6实现增删改查接口
47 1
|
3月前
|
存储 缓存 关系型数据库
Django后端架构开发:缓存机制,接口缓存、文件缓存、数据库缓存与Memcached缓存
Django后端架构开发:缓存机制,接口缓存、文件缓存、数据库缓存与Memcached缓存
70 0
|
3月前
|
druid Java 数据库连接
SpringBoot项目整合MybatisPlus持久层框架+Druid数据库连接池,以及实现增删改查功能
SpringBoot项目整合MybatisPlus和Druid数据库连接池,实现基本的增删改查功能。
343 0
|
3月前
|
前端开发 Java 关系型数据库
通过HTML网页对mysql数据库进行增删改查(CRUD实例)
通过HTML网页对mysql数据库进行增删改查(CRUD实例)
226 0
|
12天前
|
SQL 关系型数据库 MySQL
12 PHP配置数据库MySQL
路老师分享了PHP操作MySQL数据库的方法,包括安装并连接MySQL服务器、选择数据库、执行SQL语句(如插入、更新、删除和查询),以及将结果集返回到数组。通过具体示例代码,详细介绍了每一步的操作流程,帮助读者快速入门PHP与MySQL的交互。
26 1