【GoLang】《GORM实战》第二篇:CRUD之路

文章目录

    • 创建
      • 创建记录
      • 默认值
      • 扩展创建选项
      • 钩子方法
      • Upsert 及冲突
    • 查询
      • 一般查询
      • Where 条件
        • 普通SQL查询
        • Struct & Map查询
      • Not 条件
      • Or条件
      • 内联条件
      • 额外查询选项
      • FirstOrInit
        • Attrs
        • Assign
      • FirstOrCreate
        • Attrs
        • Assign
      • 高级查询
        • 子查询
        • 选择字段
        • 排序
        • 数量
        • 偏移
        • 总数
        • Group & Having
        • 连接
      • Pluck
      • 扫描
      • Scopes
    • 更新
      • 更新所有字段
      • 更新修改字段
      • 更新选定字段
      • 无Hooks更新
      • 批量更新
      • 使用SQL表达式更新
      • 修改Hooks中的值
      • 其它更新选项
    • 删除
      • 删除记录
      • 批量删除
      • 阻止全局删除
      • 返回删除数据
      • 软删除
        • Soft Delete
          • 指定时间精度
          • Flag模式
          • 混合模式
      • 物理删除
    • SQL构造器
      • 原生SQL
        • 原生SQL查询
        • 执行原生 SQL
      • 命名参数
      • DryRun 模式
      • 分句(Clauses)
        • Clause 构建器

创建

创建记录

首先定义模型:

type User struct {
	ID           int64
	Name         string
	Age          int64
}

使用使用NewRecord()查询主键是否存在,主键为空使用Create()创建记录:

user := User{Name: "zhurunhua", Age: 18}

db.NewRecord(user) // 主键为空返回`true`
db.Create(&user)   // 创建user
db.NewRecord(user) // 创建`user`后返回`false`

默认值

可以通过 tag 定义字段的默认值,比如:

type User struct {
  ID   int64
  Name string `gorm:"default:'zhurunhua'"`
  Age  int64
}

**注意:**通过tag定义字段的默认值,在创建记录时候生成的 SQL 语句会排除没有值或值为 零值 的字段。 在将记录插入到数据库后,Gorm会从数据库加载那些字段的默认值。

举个例子:

var user = User{Name: "", Age: 99}
db.Create(&user)

上面代码实际执行的SQL语句是INSERT INTO users("age") values('99');,排除了零值字段Name,而在数据库中这一条数据会使用设置的默认值zhurunhua作为Name字段的值。

**注意:**所有字段的零值, 比如0, "",false或者其它零值,都不会保存到数据库内,但会使用他们的默认值。 如果你想避免这种情况,可以考虑使用指针或实现 Scanner/Valuer接口,比如:

使用指针方式实现零值存入数据库

// 使用指针
type User struct {
  ID   int64
  Name *string `gorm:"default:'zhurunhua'"`
  Age  int64
}
user := User{Name: new(string), Age: 18))}
db.Create(&user)  // 此时数据库中该条记录name字段的值就是''

使用Scanner/Valuer接口方式实现零值存入数据库

// 使用 Scanner/Valuer
type User struct {
	ID int64
	Name sql.NullString `gorm:"default:'zhurunhua'"` // sql.NullString 实现了Scanner/Valuer接口
	Age  int64
}
user := User{Name: sql.NullString{"", true}, Age:18}
db.Create(&user)  // 此时数据库中该条记录name字段的值就是''

扩展创建选项

例如PostgreSQL数据库中可以使用下面的方式实现合并插入, 有则更新, 无则插入。

// 为Instert语句添加扩展SQL选项
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)
// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;
// 创建表时添加表后缀
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})

钩子方法

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

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

func (s Student) BeforeCreate(tx *gorm.DB) (err error) {
	if len(s.StudentNum) > 16 {
		return errors.New("学号非法")
	}
	fmt.Println("学号校验通过")
	return nil
}

func (s Student) AfterCreate(tx *gorm.DB) (err error) {
	fmt.Printf("学员:【%s】档案已创建!", s.StudentName)
	return
}

func (s Student) AfterSave(tx *gorm.DB) (err error) {
	fmt.Printf("学员:【%s】档案已保存!", s.StudentName)
	return
}

//如果要对查询出来的数据修改,使用指针类型
func (s *Student) AfterFind(tx *gorm.DB) (err error) {
	if s.Gender == 1 {
		s.GenderName = "男"
	} else if s.Gender == 2 {
		s.GenderName = "女"
	}
	return
}

Upsert 及冲突

GORM 为不同数据库提供了兼容的 Upsert 支持

	
		student := &entity.Student{
		StudentNum:  "10001",
		StudentName: "李逍遥",
		Gender:      1,
		Birthday:    "1900-01-02",
		//以下两种方式解决零值问题
		//Address:     new(string),
		//Address: sql.NullString{"", true},
	}
	
	//忽略冲突,创建不成功,也不报错
	db.Debug().Clauses(clause.OnConflict{DoNothing: true}).Create(student)

	/**
	在student_num冲突时,将name列更新为新值

	INSERT INTO `students` ( `student_num`, `name`, `gender`, `birthday`, `address`, `created_at`, `updated_at`, `deleted_at` )
	VALUES
		( '2', '李逍遥', 1, '1900-01-02', '蜀山', '2021-11-04 19:57:43.356', '2021-11-04 19:57:43.356', NULL )
		ON DUPLICATE KEY UPDATE `name` =
	VALUES
		( `name` )
	*/
	db.Debug().Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "student_num"}},
		DoUpdates: clause.AssignmentColumns([]string{"name"}),
	}).Create(getStudent())

查询

一般查询

// 根据主键查询第一条记录
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;

// 查询所有的记录
db.Find(&users)
 SELECT * FROM users;

// 查询指定的某条记录(仅当主键为整型时可用)
db.First(&user, 10)
 SELECT * FROM users WHERE id = 10;

Where 条件

普通SQL查询

// Get first matched record
db.Where("name = ?", "lixiaoyao").First(&user)
 SELECT * FROM users WHERE name = 'lixiaoyao' limit 1;

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

// <>
db.Where("name <> ?", "lixiaoyao").Find(&users)
 SELECT * FROM users WHERE name <> 'lixiaoyao';

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

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

// AND
db.Where("name = ? AND age >= ?", "lixiaoyao", "22").Find(&users)
 SELECT * FROM users WHERE name = 'lixiaoyao' 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';

Struct & Map查询

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

// Map
db.Where(map[string]interface{}{"name": "lixiaoyao", "age": 20}).Find(&users)
 SELECT * FROM users WHERE name = "lixiaoyao" 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: "lixiaoyao", Age: 0}).Find(&users)
 SELECT * FROM users WHERE name = "lixiaoyao";

你可以使用指针或实现 Scanner/Valuer 接口来避免这个问题.

// 使用指针
type User struct {
  gorm.Model
  Name string
  Age  *int
}

// 使用 Scanner/Valuer
type User struct {
  gorm.Model
  Name string
  Age  sql.NullInt64  // sql.NullInt64 实现了 Scanner/Valuer 接口
}

Not 条件

作用与 Where 类似的情形如下:

db.Not("name", "lixiaoyao").First(&user)
 SELECT * FROM users WHERE name <> "lixiaoyao" LIMIT 1;

// Not In
db.Not("name", []string{"lixiaoyao", "lixiaoyao 2"}).Find(&users)
 SELECT * FROM users WHERE name NOT IN ("lixiaoyao", "lixiaoyao 2");

// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
 SELECT * FROM users WHERE id NOT IN (1,2,3);

db.Not([]int64{}).First(&user)
 SELECT * FROM users;

// Plain SQL
db.Not("name = ?", "lixiaoyao").First(&user)
 SELECT * FROM users WHERE NOT(name = "lixiaoyao");

// Struct
db.Not(User{Name: "lixiaoyao"}).First(&user)
 SELECT * FROM users WHERE name <> "lixiaoyao";

Or条件

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
 SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'lixiaoyao'").Or(User{Name: "lixiaoyao 2"}).Find(&users)
 SELECT * FROM users WHERE name = 'lixiaoyao' OR name = 'lixiaoyao 2';

// Map
db.Where("name = 'lixiaoyao'").Or(map[string]interface{}{"name": "lixiaoyao 2"}).Find(&users)
 SELECT * FROM users WHERE name = 'lixiaoyao' OR name = 'lixiaoyao 2';

内联条件

作用与Where查询类似,当内联条件与多个立即执行方法一起使用时, 内联条件不会传递给后面的立即执行方法。

// 根据主键获取记录 (只适用于整形主键)
db.First(&user, 23)
 SELECT * FROM users WHERE id = 23 LIMIT 1;

// 根据主键获取记录, 如果它是一个非整形主键
db.First(&user, "id = ?", "string_primary_key")
 SELECT * FROM users WHERE id = 'string_primary_key' LIMIT 1;

db.Find(&user, "name = ?", "lixiaoyao")
 SELECT * FROM users WHERE name = "lixiaoyao";

db.Find(&users, "name <> ? AND age > ?", "lixiaoyao", 20)
 SELECT * FROM users WHERE name <> "lixiaoyao" 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;

额外查询选项

// 为查询 SQL 添加额外的 SQL 操作
db.Set("gorm:query_option", "FOR UPDATE").First(&user, 10)
 SELECT * FROM users WHERE id = 10 FOR UPDATE;

FirstOrInit

获取匹配的第一条记录,否则根据给定的条件初始化一个新的对象 (仅支持 struct 和 map 条件)

// 未找到
db.FirstOrInit(&user, User{Name: "non_existing"})
 user -> User{Name: "non_existing"}

// 找到
db.Where(User{Name: "lixiaoyao"}).FirstOrInit(&user)
 user -> User{Id: 111, Name: "lixiaoyao", Age: 20}
db.FirstOrInit(&user, map[string]interface{}{"name": "lixiaoyao"})
 user -> User{Id: 111, Name: "lixiaoyao", Age: 20}

Attrs

如果记录未找到,将使用参数初始化 struct.

// 未找到
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
 SELECT * FROM USERS WHERE name = 'non_existing';
 user -> User{Name: "non_existing", Age: 20}

db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user)
 SELECT * FROM USERS WHERE name = 'non_existing';
 user -> User{Name: "non_existing", Age: 20}

// 找到
db.Where(User{Name: "lixiaoyao"}).Attrs(User{Age: 30}).FirstOrInit(&user)
 SELECT * FROM USERS WHERE name = lixiaoyao';
 user -> User{Id: 111, Name: "lixiaoyao", Age: 20}

Assign

不管记录是否找到,都将参数赋值给 struct.

// 未找到
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
 user -> User{Name: "non_existing", Age: 20}

// 找到
db.Where(User{Name: "lixiaoyao"}).Assign(User{Age: 30}).FirstOrInit(&user)
 SELECT * FROM USERS WHERE name = lixiaoyao';
 user -> User{Id: 111, Name: "lixiaoyao", Age: 30}

FirstOrCreate

获取匹配的第一条记录, 否则根据给定的条件创建一个新的记录 (仅支持 struct 和 map 条件)

// 未找到
db.FirstOrCreate(&user, User{Name: "non_existing"})
 INSERT INTO "users" (name) VALUES ("non_existing");
 user -> User{Id: 112, Name: "non_existing"}

// 找到
db.Where(User{Name: "lixiaoyao"}).FirstOrCreate(&user)
 user -> User{Id: 111, Name: "lixiaoyao"}

Attrs

如果记录未找到,将使用参数创建 struct 和记录.

 // 未找到
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
 SELECT * FROM users WHERE name = 'non_existing';
 INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
 user -> User{Id: 112, Name: "non_existing", Age: 20}

// 找到
db.Where(User{Name: "lixiaoyao"}).Attrs(User{Age: 30}).FirstOrCreate(&user)
 SELECT * FROM users WHERE name = 'lixiaoyao';
 user -> User{Id: 111, Name: "lixiaoyao", Age: 20}

Assign

不管记录是否找到,都将参数赋值给 struct 并保存至数据库.

// 未找到
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
 SELECT * FROM users WHERE name = 'non_existing';
 INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
 user -> User{Id: 112, Name: "non_existing", Age: 20}

// 找到
db.Where(User{Name: "lixiaoyao"}).Assign(User{Age: 30}).FirstOrCreate(&user)
 SELECT * FROM users WHERE name = 'lixiaoyao';
 UPDATE users SET age=30 WHERE id = 111;
 user -> User{Id: 111, Name: "lixiaoyao", Age: 30}

高级查询

子查询

子查询可以嵌套在查询中,GORM 允许在使用 *gorm.DB 对象作为参数时生成子查询

db.Where("amount > ?", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")

选择字段

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;

排序

Order,指定从数据库中检索出记录的顺序。设置第二个参数 reorder 为 true ,可以覆盖前面定义的排序条件。

db.Order("age desc, name").Find(&users)
 SELECT * FROM users ORDER BY age desc, name;

// 多字段排序
db.Order("age desc").Order("name").Find(&users)
 SELECT * FROM users ORDER BY age desc, name;

// 覆盖排序
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
 SELECT * FROM users ORDER BY age desc; (users1)
 SELECT * FROM users ORDER BY age; (users2)

数量

Limit,指定从数据库检索出的最大记录数。

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)

偏移

Offset,指定开始返回记录前要跳过的记录数。

db.Offset(3).Find(&users)
 SELECT * FROM users OFFSET 3;

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

总数

Count,该 model 能获取的记录总数。

db.Where("name = ?", "lixiaoyao").Or("name = ?", "lixiaoyao 2").Find(&users).Count(&count)
 SELECT * from USERS WHERE name = 'lixiaoyao' OR name = 'lixiaoyao 2'; (users)
 SELECT count(*) FROM users WHERE name = 'lixiaoyao' OR name = 'lixiaoyao 2'; (count)

db.Model(&User{}).Where("name = ?", "lixiaoyao").Count(&count)
 SELECT count(*) FROM users WHERE name = 'lixiaoyao'; (count)

db.Table("deleted_users").Count(&count)
 SELECT count(*) FROM deleted_users;

db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
 SELECT count( distinct(name) ) FROM deleted_users; (count)

注意 Count 必须是链式查询的最后一个操作 ,因为它会覆盖前面的 SELECT,但如果里面使用了 count 时不会覆盖

Group & Having

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
  ...
}

// 使用Scan将多条结果扫描进事先准备好的结构体切片中
type Result struct {
	Date time.Time
	Total int
}
var rets []Result
db.Table("users").Select("date(created_at) as date, sum(age) as total").Group("date(created_at)").Scan(&rets)

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)

连接

Joins,指定连接条件

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 = ?", "[email protected]").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)

Pluck

Pluck,查询 model 中的一个列作为切片,如果想要查询多个列,应该使用Scan或者Find

var ages []int64
db.Find(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

// 想查询多个字段? 这样做:
db.Select("name, age").Find(&users)

扫描

Scan,扫描结果至一个 struct,用法类似Find

type Result struct {
  Name string
  Age  int
}

var result Result
db.Table("users").Select("name, age").Where("name = ?", "Antonio").Scan(&result)

var results []Result
db.Table("users").Select("name, age").Where("id > ?", 0).Scan(&results)

// 原生 SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)

Scopes

Scopes,Scope是建立在链式操作的基础之上的。

基于它,你可以抽取一些通用逻辑,写出更多可重用的函数库。

func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
  return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "C")
}

func PaidWithCod(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "C")
}

func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
  return func (db *gorm.DB) *gorm.DB {
    return db.Scopes(AmountGreaterThan1000).Where("status IN (?)", status)
  }
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查找所有金额大于 1000 的信用卡订单

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// 查找所有金额大于 1000 的 COD 订单

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// 查找所有金额大于 1000 且已付款或者已发货的订单

分页

func Paginate(r *http.Request) func(db *gorm.DB) *gorm.DB {
  return func (db *gorm.DB) *gorm.DB {
    page, _ := strconv.Atoi(r.Query("page"))
    if page == 0 {
      page = 1
    }

    pageSize, _ := strconv.Atoi(r.Query("page_size"))
    switch {
    case pageSize > 100:
      pageSize = 100
    case pageSize <= 0:
      pageSize = 10
    }

    offset := (page - 1) * perPasge
    return db.Offset(offset).Limit(pageSize)
  }
}

db.Scopes(Paginate(r)).Find(&users)
db.Scopes(Paginate(r)).Find(&articles)

更新

更新所有字段

Save()默认会更新该对象的所有字段,即使你没有赋值。

db.First(&user)

user.Name = "七米"
user.Age = 99
db.Save(&user)

  UPDATE `users` SET `created_at` = '2020-02-16 12:52:20', `updated_at` = '2020-02-16 12:54:55', `deleted_at` = NULL, `name` = '七米', `age` = 99, `active` = true  WHERE `users`.`deleted_at` IS NULL AND `users`.`id` = 1

更新修改字段

如果你只希望更新指定字段,可以使用Update或者Updates

// 更新单个属性,如果它有变化
db.Model(&user).Update("name", "hello")
 UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据给定的条件更新单个属性
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;

// 使用 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 更新多个属性,只会更新其中有变化且为非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})
 UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// 警告:当使用 struct 更新时,GORM只会更新那些非零值的字段
// 对于下面的操作,不会发生任何更新,"", 0, false 都是其类型的零值
db.Model(&user).Updates(User{Name: "", Age: 0, Active: false})

更新选定字段

如果你想更新或忽略某些字段,你可以使用 SelectOmit

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

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

无Hooks更新

上面的更新操作会自动运行 model 的 BeforeUpdate, AfterUpdate 方法,更新 UpdatedAt 时间戳, 在更新时保存其 Associations, 如果你不想调用这些方法,你可以使用 UpdateColumnUpdateColumns

// 更新单个属性,类似于 `Update`
db.Model(&user).UpdateColumn("name", "hello")
 UPDATE users SET name='hello' WHERE id = 111;

// 更新多个属性,类似于 `Updates`
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
 UPDATE users SET name='hello', age=18 WHERE id = 111;

批量更新

批量更新时Hooks(钩子函数)不会运行。

db.Table("users").Where("id IN (?)", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
 UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
db.Model(User{}).Updates(User{Name: "hello", Age: 18})
 UPDATE users SET name='hello', age=18;

// 使用 `RowsAffected` 获取更新记录总数
db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected

使用SQL表达式更新

先查询表中的第一条数据保存至user变量。

var user User
db.First(&user)
db.Model(&user).Update("age", gorm.Expr("age * ? + ?", 2, 100))
 UPDATE `users` SET `age` = age * 2 + 100, `updated_at` = '2020-02-16 13:10:20'  WHERE `users`.`id` = 1;

db.Model(&user).Updates(map[string]interface{}{"age": gorm.Expr("age * ? + ?", 2, 100)})
 UPDATE "users" SET "age" = age * '2' + '100', "updated_at" = '2020-02-16 13:05:51' WHERE `users`.`id` = 1;

db.Model(&user).UpdateColumn("age", gorm.Expr("age - ?", 1))
 UPDATE "users" SET "age" = age - 1 WHERE "id" = '1';

db.Model(&user).Where("age > 10").UpdateColumn("age", gorm.Expr("age - ?", 1))
 UPDATE "users" SET "age" = age - 1 WHERE "id" = '1' AND quantity > 10;

修改Hooks中的值

如果你想修改 BeforeUpdate, BeforeSave 等 Hooks 中更新的值,你可以使用 scope.SetColumn, 例如:

func (user *User) BeforeSave(scope *gorm.Scope) (err error) {
  if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
    scope.SetColumn("EncryptedPassword", pw)
  }
}

其它更新选项

// 为 update SQL 添加其它的 SQL
db.Model(&user).Set("gorm:update_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Update("name", "hello")
 UPDATE users SET name='hello', updated_at = '2013-11-17 21:34:10' WHERE id=111 OPTION (OPTIMIZE FOR UNKNOWN);

删除

删除记录

警告 删除记录时,请确保主键字段有值,GORM 会通过主键去删除记录,如果主键为空,GORM 会删除该 model 的所有记录。

// 删除现有记录
db.Delete(&email)
 DELETE from emails where id=10;

// 为删除 SQL 添加额外的 SQL 操作
db.Set("gorm:delete_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Delete(&email)
 DELETE from emails where id=10 OPTION (OPTIMIZE FOR UNKNOWN);

批量删除

删除全部匹配的记录

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

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

阻止全局删除

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

可以使用添加条件、原生SQL、允许AllowGlobalUpdate等方式来操作

db.Delete(&User{}).Error // gorm.ErrMissingWhereClause

db.Where("1 = 1").Delete(&User{})
// DELETE `users` WHERE 1=1

db.Exec("DELETE FROM users")
// DELETE FROM users

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
// DELETE FROM users

返回删除数据

该功能需要数据库支持

// 返回所有列
var users []User
DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)
// DELETE FROM `users` WHERE role = "admin" RETURNING *
// users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}

// 返回指定列
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`
// users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}

软删除

如果一个 model 有 DeletedAt 字段,他将自动获得软删除的功能! 当调用 Delete 方法时, 记录不会真正的从数据库中被删除, 只会将DeletedAt 字段的值会被设置为当前时间

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;

// Unscoped 方法可以查询被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
 SELECT * FROM users WHERE age = 20;

Soft Delete

import "gorm.io/plugin/soft_delete"

type User struct {
  ID        uint
  Name      string
  DeletedAt soft_delete.DeletedAt
}

// Query
SELECT * FROM users WHERE deleted_at = 0;

// Delete
UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;
指定时间精度

deleted_at字段支持毫秒、纳秒级的时间戳精度,通过设置标签:gorm:“softDelete:milli”、gorm:“softDelete:nano”

type User struct {
  ID    uint
  Name  string
  DeletedAt soft_delete.DeletedAt `gorm:"softDelete:milli"`
  // DeletedAt soft_delete.DeletedAt `gorm:"softDelete:nano"`
}

// Query
SELECT * FROM users WHERE deleted_at = 0;

// Delete
UPDATE users SET deleted_at = /* current unix milli second or nano second */ WHERE ID = 1;
Flag模式

flag模式将使用 0, 1 来标记数据是否为删除, 1 标识已删除

type User struct {
  ID    uint
  Name  string
  IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}

// Query
SELECT * FROM users WHERE is_del = 0;

// Delete
UPDATE users SET is_del = 1 WHERE ID = 1;
混合模式

混合模式使用 0, 1 来标记数据是否为删除, 1 标识已删除,并且存储删除时间

type User struct {
  ID        uint
  Name      string
  DeletedAt uint
  IsDel     soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"`
}

// Query
SELECT * FROM users WHERE is_del = 0;

// Delete
UPDATE users SET is_del = 1, deleted_at = /* current unix second */ WHERE ID = 1;

物理删除

// Unscoped 方法可以物理删除记录
db.Unscoped().Delete(&order)
 DELETE FROM orders WHERE id=10;

SQL构造器

原生SQL

原生SQL查询

type Result struct {
  ID   int
  Name string
  Age  int
}

var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

var age int
DB.Raw("select sum(age) from users where role = ?", "admin").Scan(&age)

执行原生 SQL

db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{1,2,3})

// SQL 表达式
DB.Exec("update users set money=? where name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")

命名参数

GORM 支持 sql.NamedArgmap[string]interface{}{} 命名参数,例如:

DB.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"

DB.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu2"}).First(&result3)
// SELECT * FROM `users` WHERE name1 = "jinzhu2" OR name2 = "jinzhu2" ORDER BY `users`.`id` LIMIT 1

DB.Raw("SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name", sql.Named("name", "jinzhu1"), sql.Named("name2", "jinzhu2")).Find(&user)
// SELECT * FROM users WHERE name1 = "jinzhu1" OR name2 = "jinzhu2" OR name3 = "jinzhu1"

DB.Exec("UPDATE users SET name1 = @name, name2 = @name2, name3 = @name", sql.Named("name", "jinzhunew"), sql.Named("name2", "jinzhunew2"))
// UPDATE users SET name1 = "jinzhunew", name2 = "jinzhunew2", name3 = "jinzhunew"

DB.Raw("SELECT * FROM users WHERE (name1 = @name AND name3 = @name) AND name2 = @name2", map[string]interface{}{"name": "jinzhu", "name2": "jinzhu2"}).Find(&user)
// SELECT * FROM users WHERE (name1 = "jinzhu" AND name3 = "jinzhu") AND name2 = "jinzhu2"

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}

分句(Clauses)

GORM 内部使用 SQL builder 生成 SQL。对于每个操作,GORM 都会创建一个 *gorm.Statement 对象,所有的 GORM API 都是在为 statement 添加/修改 Clause,最后,GORM 会根据这些 Clause 生成 SQL

例如,当通过 First 进行查询时,它会在 Statement 中添加以下 Clause

clause.Select{Columns: "*"}
clause.From{Tables: clause.CurrentTable}
clause.Limit{Limit: 1}
clause.OrderByColumn{
  Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
}

然后 GORM 在回调中构建最终的查询 SQL,像这样:

Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR")

生成 SQL:

SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

Clause 构建器

不同的数据库, Clause 可能会生成不同的 SQL,例如:

db.Offset(10).Limit(5).Find(&users)
// SQL Server 会生成
// SELECT * FROM "users" OFFSET 10 ROW FETCH NEXT 5 ROWS ONLY
// MySQL 会生成
// SELECT * FROM `users` LIMIT 5 OFFSET 10

之所以支持 Clause,是因为 GORM 允许数据库驱动程序通过注册 Clause Builder 来取代默认值,这儿有一个 Limit 的示例

参考:
https://gorm.io/docs/index.html
https://www.liwenzhou.com/posts/Go/gorm/

你可能感兴趣的:(Golang,golang,数据库,Gorm)