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

2023年1月20日10:28:55

前言

感谢开源项目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` ...

您也可以通过 SelectOmit 跳过关联保存,例如:

	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 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 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
  • 作者:lin钟一
  • 原文链接:https://blog.csdn.net/weixin_46618592/article/details/125740498
    更新时间:2023年1月20日10:28:55 ,共 8958 字。