对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,采用元数据来描述对象与关系映射的细节。只要提供了持久化类与表的映射关系,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倾向于约定大于配置,默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间
针对上述约定,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特性,支持根据Go Struct结构自动生成对应的表结构。
// 根据User结构体,自动创建表结构.
db.AutoMigrate(&User{})
注意:仅支持建表,不支持修改字段和删除字段,避免意外导致丢失数据
更多使用详见:https://www.cnblogs.com/infodriven/p/16351624.html
// 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)
创建单条
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,
}})
}
通过对比上述SQL生成情况,可以发现使用Map的方式,仅支持非零值,而且Gorm中约定的CreateAt等均不生效,完全依赖Map中值。
// 根据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"})
// 唯一约束不发挥作用,没啥用
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)
保存所有的字段,即使字段是零值。
如果我们传入的结构主键为零值,则会插入记录
如果传入主键ID不为空且使用slice方式保存,则可以实现ON DUPLICATE KEY UPDATE 唯一键约束 此时效果等于Upsert
如果传入主键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来代替
目前看有三种方式,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)
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))
默认情况下,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))
支持根据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不可以
// 获取第一条记录(主键升序)
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
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);
db.Distinct("name", "age").Order("name, age desc").Find(&results)
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;
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;
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
获取第一条匹配的记录,或者根据给定的条件初始化一个实例(仅支持 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}
获取匹配的第一条记录或者根据给定条件创建一条新记录(仅 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}
原生查询SQL和scan
var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
日常写法
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关联查询