【Gorm】增删改查使用入门

何为ORM?

对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,采用元数据来描述对象与关系映射的细节。只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。

ORM框架分类

传统的 jdbc 是手工的,需要程序员加载驱动、建立连接、创建 Statement 对象、定义SQL语句、处理返回结果、关闭连接等操作

ORM分为两种类型,全自动ORM和半自动ORM,以Java中常见的两种ORM框架举例:

  • Hibernate 是自动化的,内部封装了JDBC,连 SQL 语句都封装了,理念是即使开发人员不懂SQL语言也可以进行开发工作,向应用程序提供调用接口,直接调用即可。

  • Mybatis 是半自动化的,是介于 jdbc 和 Hibernate之间的持久层框架,也是对 JDBC 进行了封装,不过将SQL的定义工作独立了出来交给用户实现,负责完成剩下的SQL解析,处理等工作。

两者根本区别

  • 全自动ORM不需要手动编写SQL,只需要操作相应对象即可,大大降低了对象与数据库的耦合性,而半自动ORM需要手动编写 SQL,可移植性全自动框架比半自动框架更高

  • 半自动框架支持动态SQL,处理列表,存储过程,开发工作量相对大些;全自动框架提供了对应语言操作数据库,如果项目需要支持多种数据库,代码开发量少,但 SQL语句的优化困难


模型定义

模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成

简单来说就是我们日常开发中定义的结构体

type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

gorm.Model

gorm倾向于约定大于配置,默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间

针对上述约定,GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt

// gorm.Model 的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

可以把gorm.Model嵌入自定义结构体中

type User struct {
  gorm.Model
  Name string
}
// 等效于
type User struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
  Name string
}

这样做有什么好处?

Create和Update的时候自动填充创建时间和更新时间,在创建的时候自动检测主键冲突,如果冲突则更新

在查询的时候自动保证数据不是被逻辑删除的

// Create
db.Create(&Product{Code: "D42", Price: 100})
// Read
var product Product
db.Debug().First(&product, 1)                 // 根据整形主键查找

对应的SQL

当需要逻辑删除的时候,不需要额外编写update语句去更新,直接使用gorm中封装的Delete即可。

// Delete - 删除 product
db.Debug().Delete(&product)

对应的SQL

如果不想使用gorm.Model该如何达到上述效果?

字段标签

声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase 风格

type User struct {
  CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充
  UpdatedAt int       // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
  Updated   int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳纳秒数填充更新时间
  Updated   int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间
  Created   int64 `gorm:"autoCreateTime"`      // 使用时间戳秒数填充创建时间
}

更多标签:https://gorm.io/zh_CN/docs/models.html#embedded_struct

https://gorm.io/zh_CN/docs/associations.html#tags

Question:为什么支持这么多字段标签?

Migration特性

支持Migration特性,支持根据Go Struct结构自动生成对应的表结构。

// 根据User结构体,自动创建表结构.
db.AutoMigrate(&User{})

注意📢:仅支持建表,不支持修改字段和删除字段,避免意外导致丢失数据

更多使用详见:https://www.cnblogs.com/infodriven/p/16351624.html


Create

  • 单条插入

// Create
product := Product{Code: "D42", Price: 100}
result := db.Create(&product) // // 通过数据的指针来创建
product.ID                    // 主键id
result.Error                  //返回 error
result.RowsAffected           // 插入返回次数
  • 插入指定字段

插入全部字段
p2 := Product{Code: "D48", Price: 100, Count: 20}
db.Debug().Create(&p2)

插入指定字段
p3 := Product{Code: "D49", Price: 100, Count: 20}
db.Debug().Select("code", "count").Create(&p3)
  • 插入忽略指定字段

p4 := Product{Code: "D50", Price: 100, Count: 20}
db.Debug().Create(&p4)

插入忽略指定字段
p5 := Product{Code: "D51", Price: 100, Count: 20}
db.Debug().Omit("code", "count").Create(&p5)
  • 批量插入

slice传递给Create方法,Gorm将生成单独一条SQL语句插入所有数据,并回填主键
var productList1 = []Product{{Code: "D75"}, {Code: "D76"}, {Code: "D87"}}
db.Debug().Create(&productList1)

使用CreateInBatches可以分批创建,可以自定指定每批的数量
var productList2 = []Product{{Code: "D85"}, {Code: "D86"}, {Code: "D87"}}
db.Debug().CreateInBatches(&productList2, 2)
  • 根据Map创建

创建单条

    db.Debug().Model(&Product{}).Create(map[string]interface{}{
      "Code": "D91", "Price": 18,
   })

}

创建多条

    db.Debug().Model(&Product{}).Create([]map[string]interface{}{{
      "Code": "D91", "Price": 18,
   },
      {
         "Code": "D92", "Price": 19,
      }})
}

Question:Map和其他有什么区别?

通过对比上述SQL生成情况,可以发现使用Map的方式,仅支持非零值,而且Gorm中约定的CreateAt等均不生效,完全依赖Map中值。


Update

  • 更新单列

// 根据where条件更新
result := db.Debug().Model(&Product{}).Where("code = ?", "A42").Update("price", 1000)
fmt.Println(fmt.Sprintf("影响行数:%d", result.RowsAffected))


model := gorm.Model{
   ID: 119,
}
product := Product{Code: "A42", Price: 100, Model: model}
// 根据Model中ID更新
result1 := db.Debug().Model(&product).Update("price", 1000)
fmt.Println(fmt.Sprintf("影响行数:%d", result1.RowsAffected))

// 根据Model中ID和where条件更新
result2 := db.Debug().Model(&product).Where("code = ?", "A42").Update("price", 300)
fmt.Println(fmt.Sprintf("影响行数:%d", result2.RowsAffected))
  • 更新多列

model := gorm.Model{
   ID: 119,
}
product := Product{Code: "A242", Price: 234, Count: 0, Model: model}
// 使用struct更新
result1 := db.Debug().Model(&product).Updates(product)
fmt.Println(fmt.Sprintf("影响行数:%d", result1.RowsAffected))
// 使用Map更新
result2 := db.Debug().Model(&product).Updates(map[string]interface{}{"price": 0, "code": "W101"})
fmt.Println(fmt.Sprintf("影响行数:%d", result2.RowsAffected))

结合上述两种可以发现,针对零值的处理,Gorm只会更新非零值的字段,针对非零值,需要使用Map的方式更新

  • 更新指定字段

product := Product{Code: "A242", Price: 234, Count: 0, Model: model}
// struct方式 更新指定字段,即使值是零值
db.Debug().Model(&product).Select("code", "count").Updates(product)
// struct方式 忽略更新指定字段,不包含非零值
db.Debug().Model(&product).Omit("code").Updates(product)

// map方式 更新指定字段,即使值是零值
db.Debug().Model(&product).Select("code", "price").Updates(map[string]interface{}{"price": 0, "code": "W101"})
// map方式 忽略更新指定字段,包含零值
db.Debug().Model(&product).Omit("code").Updates(map[string]interface{}{"price": 0, "code": "W101"})

能否使用select的方式,更新全部字段,包括零值?

// struct方式 更新全部字段,即使是零值,注意:此方式创建时间如果没填入,一并会覆盖
db.Debug().Model(&product).Select("*").Updates(&product)

// struct方式,更新全部字段,除去指定字段
db.Debug().Model(&product).Select("*").Omit("created_at").Updates(&product)
  • 批量更新

product := Product{Code: "A242", Price: 234, Count: 0, Model: model}
// struct方式
db.Debug().Model(Product{}).Where("price = ?", 100).Updates(product)
// map
db.Debug().Table("products").Where("id in ?", []int{1, 2, 3}).Updates(map[string]interface{}{"price": 0, "code": "W101"})

CreateOrUpdate

  • Upsert

// 唯一约束不发挥作用,没啥用
model := gorm.Model{
   ID: 113,
}
product := Product{Code: "A42", Price: 100, Model: model}
db.Debug().Clauses(clause.OnConflict{DoNothing: true}).Create(&product)

//更新列 map的方式更新列 默认使用主键id做约束键 conflict
db.Debug().Clauses(clause.OnConflict{
   Columns:   []clause.Column{{Name: "id"}},
   DoUpdates: clause.Assignments(map[string]interface{}{"code": "A42", "price": "200"}),
}).Create(&product)

// 更新列 利用结构体中值做更新 默认使用主键id做约束键 conflict
product.Price = 500
db.Debug().Clauses(clause.OnConflict{
   Columns:   []clause.Column{{Name: "id"}},
   DoUpdates: clause.AssignmentColumns([]string{"code", "price"}),
}).Create(&product)

// 使用结构体中值更新所有列,使用主键id做约束键 conflict
db.Debug().Clauses(clause.OnConflict{
   UpdateAll: true,
}).Create(&product)
  • Save

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

  1. 如果我们传入的结构主键为零值,则会插入记录

  1. 如果传入主键ID不为空且使用slice方式保存,则可以实现ON DUPLICATE KEY UPDATE 唯一键约束 此时效果等于Upsert

  1. 如果传入主键ID不为空且使用struct方式保存,则效果等于Update

// 传入主键ID为空,则默认执行插入操作
p := Product{Price: 200, Code: "U109"}
db.Debug().Save(&p)

// 如果传入主键ID不为空,且使用slice方式保存,则可以实现ON DUPLICATE KEY UPDATE 唯一键约束
model1 := gorm.Model{
   ID: 113,
}
p1 := Product{Price: 200, Code: "U103", Model: model1}
products := []Product{p1}
db.Debug().Save(&products)

// 如果传入主键ID不为空,且使用struct方式保存,则执行update操作
model2 := gorm.Model{
   ID: 114,
}
p2 := Product{Price: 200, Code: "U103", Model: model2}
db.Debug().Save(&p2)

Save会根据Struct有策略自行选择,所以在生产环境不建议使用,使用Create,Update,以及Upsert来代替

Question:如何保存和更新零值?

目前看有三种方式,map+update,select+update,save

save,默认更新struct所有内容,即使设置了omit也不能生效

model := gorm.Model{
   ID: 119,
}
p := Product{Price: 200, Code: "U109", Model: model}
db.Debug().Save(&p)

db.Debug().Omit("create_at").Save(&p)

map+update,需要自己设置db中字段和值的对应关系

result2 := db.Debug().Model(&product).Updates(map[string]interface{}{"price": 0, "code": "W101"})

struct+select+update,再配合使用omit,既可以更新全部字段包括零值,也能通过利用omit忽略指定字段

db.Debug().Model(&product).Select("*").Omit("created_at").Updates(&product)

Delete

type Product struct {
   //gorm.Model
   ID    uint `gorm:"primarykey"`
   Code  string
   Price uint
   Count int64
}

物理删除

  • 主键删除

// 根据id删除
result1 := db.Debug().Delete(&Product{}, 20)
fmt.Println(fmt.Sprintf("影响行数:%d", result1.RowsAffected))

// id被编译成了字符串
result2 := db.Debug().Delete(&Product{}, "100")
fmt.Println(fmt.Sprintf("影响行数:%d", result2.RowsAffected))

// 根据主键id批量删除
var productList []Product
result3 := db.Debug().Delete(&productList, []int{50, 51, 52})
fmt.Println(fmt.Sprintf("影响行数:%d", result3.RowsAffected))
  • 批量删除

result := db.Debug().Where("code = ?", "A42").Delete(&Product{})
//result.Error // 错误记录
fmt.Println(fmt.Sprintf("影响行数:%d", result.RowsAffected))
  • 全局删除

Gorm默认是阻止全局删除的,返回ErrMissingWhereClause,即Delete from xxx 是无法执行成功的

db.Where("1 = 1").Delete(&Product{})
// DELETE FROM `products` WHERE 1=1

原生SQL
db.Exec("DELETE FROM products")
// DELETE FROM products

逻辑删除

type Product struct {
   gorm.Model
   Code  string
   Price uint
   Count int64
}

Struct中包含gorm.deletedat字段(gorm.Model 已经包含了该字段),它将自动获得软删除能力。此时调用Delete,不会被真正的删除,DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。

// 根据id删除
result1 := db.Debug().Delete(&Product{}, 55)
fmt.Println(fmt.Sprintf("影响行数:%d", result1.RowsAffected))

// id被编译成了字符串
result2 := db.Debug().Delete(&Product{}, "101")
fmt.Println(fmt.Sprintf("影响行数:%d", result2.RowsAffected))

// 根据主键id批量删除
var productList []Product
result3 := db.Debug().Delete(&productList, []int{60, 61, 62})
fmt.Println(fmt.Sprintf("影响行数:%d", result3.RowsAffected))

Delete Flag

默认情况下,gorm.Model 使用 *time.Time 作为 DeletedAt 字段的值。此外,通过 gorm.io/plugin/soft_delete 插件还支持其它数据格式。

使用1/0作为Delete Flag

import (
   "fmt"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/clause"
   "gorm.io/plugin/soft_delete"
)

type Product struct {
   //gorm.Model
   ID       uint `gorm:"primarykey"`
   Code     string
   Price    uint
   Count    int64
   IsDelete soft_delete.DeletedAt `gorm:"softDelete:flag"`
}

// 根据id删除
result1 := db.Debug().Delete(&Product{}, 55)
fmt.Println(fmt.Sprintf("影响行数:%d", result1.RowsAffected))

// id被编译成了字符串
result2 := db.Debug().Delete(&Product{}, "101")
fmt.Println(fmt.Sprintf("影响行数:%d", result2.RowsAffected))

// 根据主键id批量删除
var productList []Product
result3 := db.Debug().Delete(&productList, []int{60, 61, 62})
fmt.Println(fmt.Sprintf("影响行数:%d", result3.RowsAffected))

Unix 时间戳

type Product struct {
   //gorm.Model
   ID        uint `gorm:"primarykey"`
   Code      string
   Price     uint
   Count     int64
   DeletedAt soft_delete.DeletedAt `gorm:"softDelete:milli"`
}

// 根据id删除
result1 := db.Debug().Delete(&Product{}, 55)
fmt.Println(fmt.Sprintf("影响行数:%d", result1.RowsAffected))

// id被编译成了字符串
result2 := db.Debug().Delete(&Product{}, "101")
fmt.Println(fmt.Sprintf("影响行数:%d", result2.RowsAffected))

// 根据主键id批量删除
var productList []Product
result3 := db.Debug().Delete(&productList, []int{60, 61, 62})
fmt.Println(fmt.Sprintf("影响行数:%d", result3.RowsAffected))

Read

支持根据Model和Table两种查询

var product Product
db.Debug().Model(&Product{}).First(&product)
fmt.Println(fmt.Sprintf("product:%+v", product))

var product1 Product
db.Debug().Table("products").First(&product1)
fmt.Println(fmt.Sprintf("product:%+v", product1))

Model和Table有什么区别?

result := map[string]interface{}{}
db.Debug().Model(&Product{}).First(&result)
fmt.Println(fmt.Sprintf("product:%+v", result))

result1 := map[string]interface{}{}
db.Debug().Table("products").First(&result1)
fmt.Println(fmt.Sprintf("product:%+v", result1))

当使用Map接收DB查询数据时,Model可以实现struct和map自动转换,但是Map不可以

  • 检索单个对象,仅针对Struct有效

// 获取第一条记录(主键升序)
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 or nil

// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

如果想规避ErrRecordNotFound,可以使用Find,db.limit(1).Find(&user),Find可以接受struct和slice

  • 根据主键检索,仅针对Struct有效

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

var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;
  • 条件检索

  • String条件

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

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

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

// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";

db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" 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;
  • Not条件

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

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

// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
  • 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 = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
  • Distinct

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

db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;

// Cancel limit condition with -1
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;
  • Count

db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)
  • 排序

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

// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
  • Group By & Having

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` LIMIT 1
  • 选择特定字段

db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;

db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;
  • 智能选择字段

GORM 允许通过 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
  • FirstOrInit

获取第一条匹配的记录,或者根据给定的条件初始化一个实例(仅支持 sturct 和 map 条件)

// 未找到 user,则根据给定的条件初始化一条记录
db.FirstOrInit(&user, User{Name: "non_existing"})
// user -> User{Name: "non_existing"}

// 找到了 `name` = `jinzhu` 的 user
db.Where(User{Name: "jinzhu"}).FirstOrInit(&user)
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}

// 找到了 `name` = `jinzhu` 的 user
db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}

如果没有找到记录,可以使用包含更多的属性的结构体初始化 user,Attrs 不会被用于生成查询 SQL

// 未找到 user,则根据给定的条件以及 Attrs 初始化 user
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20}

// 未找到 user,则根据给定的条件以及 Attrs 初始化 user
db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user)
// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20}

// 找到了 `name` = `jinzhu` 的 user,则忽略 Attrs
db.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 20}).FirstOrInit(&user)
// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 18}

不管是否找到记录,Assign 都会将属性赋值给 struct,但这些属性不会被用于生成查询 SQL,也不会被保存到数据库

// 未找到 user,根据条件和 Assign 属性初始化 struct
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)
// user -> User{Name: "non_existing", Age: 20}

// 找到 `name` = `jinzhu` 的记录,依然会更新 Assign 相关的属性
db.Where(User{Name: "Jinzhu"}).Assign(User{Age: 20}).FirstOrInit(&user)
// SELECT * FROM USERS WHERE name = jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "Jinzhu", Age: 20}
  • FirstOrCreate

获取匹配的第一条记录或者根据给定条件创建一条新记录(仅 struct, map 条件有效),RowsAffected 返回创建、更新的记录数

FirstOrCreate与FirstOrInit区别,如果找不到对应的记录则会根据给定的条件创建一条新记录

// 未找到 User,根据给定条件创建一条新纪录
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 1

// 找到 `name` = `jinzhu` 的 User
result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
// user -> User{ID: 111, Name: "jinzhu", "Age": 18}
// result.RowsAffected // => 0

// 未找到 user,根据条件和 Assign 属性创建记录
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// 找到了 `name` = `jinzhu` 的 user,则忽略 Attrs
db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "jinzhu", Age: 18}

// 未找到 user,根据条件和 Assign 属性创建记录
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
// user -> User{ID: 112, Name: "non_existing", Age: 20}

// 找到了 `name` = `jinzhu` 的 user,依然会根据 Assign 更新记录
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 20}).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "jinzhu", Age: 20}
  • Scan

原生查询SQL和scan

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

Question:gorm写法和日常写有何不同?

日常写法
conn.Table(model.xxx{}.TableName()).
   Where("wac_account = ? and vac_account in ? and status = 0", wacAccount, vacAccounts)

日常我们使用的是table的方式,上述基本都使用Model的方式

我们可以直接这样用吗?

约定大于配置

GORM 使用结构体名的 蛇形命名 作为表名。对于结构体 User,根据约定,其表名为 users

也可以实现 Tabler 接口来更改默认表名,例如:

type Product struct {
   gorm.Model
   Code  string
   Price uint
   Count int64
}

func (Product) TableName() string {
   return "product"
}

更多约定:https://gorm.io/zh_CN/docs/conventions.html

总结

gorm中约定大于配置,主要是gorm.Model得使用,配合字段标签等内容,在通用字段的处理上会更便利,针对Create和Update以及CreateOrUpdate,三者合理使用,可以解决存在时更新,不存在时插入,以及如果保存更新零值等常见场景。

后续更新计划

  • gorm gen

  • gorm的事务

  • gorm关联查询

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mandy_i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值