ORM是“对象-关系-映射”的简称,Go语言中常用的ORM框架有很多,如gorm、facebook-ent、xorm、upper/db、gorose等,这里以gorm为例。
gorm
注: 本文内容基于官方文档, 由于迭代原因,建议以最新官方文档为主
老牌国产Golang orm框架。支持主流关系型数据库。中文文档适合新人入手,国内使用较多。最新版本2.x,比1.x有较大改动
注意:Gorm最新地址为https://github.com/go-gorm/gorm,之前https://github.com/jinzhu/gorm地址为v1旧版本
Gorm最新源码地址:https://github.com/go-gorm/gorm
V1版本地址:https://github.com/jinzhu/gorm
中文文档地址:https://gorm.io/zh_CN/
Gorm 是 Golang 的一个 orm 框架。ORM 是通过实例对象的语法,完成关系型 数据库的操作,是"对象-关系映射"(Object/Relational Mapping) 的缩写。使用 ORM 框架可以让我们更方便的操作数据库。
Gorm官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server
作者是中国人,中文文档齐全,对开发者友好,支持主流数据库。
go get -u gorm.io/gorm // gorm
go get gorm.io/driver/mysql // 驱动
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
// loc 设置本地系统的时间, 前提 parseTime=True
// 更多参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name
_, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
//db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger:logger.Default.LogMode(logger.Info)}) 打印sql日志
if err != nil {
panic(err) // 如果数据库不存在会报错
}
}
**注意:**想要正确的处理
time.Time
,您需要带上parseTime
参数, (更多参数) 要支持完整的 UTF-8 编码,您需要将charset=utf8
更改为charset=utf8mb4
查看 获取详情
MySQl 驱动程序提供了 一些高级配置 可以在初始化过程中使用,例如: ······-
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{})
Gorm 有一个 默认 logger 实现,默认情况下,它会打印慢 SQL 和错误
Logger 接受的选项不多,您可以在初始化时自定义它,例如:
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Silent, // 日志级别 不输出日志
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: false, // 禁用彩色打印
},
)
// 全局模式
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger
})
// 新建会话模式
tx := db.Session(&Session{Logger: newLogger})
GORM 定义了这些日志级别:Silent
、Error
、Warn
、Info
db, err := gorm.Open(mysql.Open("test.db"), &gorm.Config{Logger: newLogger})
Debug
Debug 单个操作,将当前操作的 log 级别调整为 logger.Info
db.Debug().Where("name = ?", "jinzhu").First(&User{})
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
type User struct {
ID int
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err) // 如果数据库不存在会报错
}
// 数据库迁移
db.AutoMigrate(&User{})
}
AutoMigrate自动创建表,可以打开Navicat查看表结构
AutoMigrate 自动创建表目前而言还有部分bug,没有Django的完善,例如: 无法自动识别修改字段, 无法删除表字段,并且有以下特殊约定
- 默认以结构体名字小写 +s 作为表名
- 字段名go中是驼峰,mysql中换成下划线
- go中字段类型会自动映射成mysql的不同类型,有默认的对应关系
gorm.Model结构体嵌套
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err) // 如果数据库不存在会报错
}
db.AutoMigrate(&Product{}) // 可以加多个
// 可以通过Set设置附加参数,下面设置表的存储引擎为InnoDB
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
}
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err) // 如果数据库不存在会报错
}
//1 迁移 表,可以写多个
//db.AutoMigrate(&Product{})
//2 插入数据
db.Create(&Product{Code: "D42", Price: 100})
db.Create(&Product{Code: "D43", Price: 150})
//3 查询数据
var product Product // 定义空Product结构体对象
db.First(&product, 1) // 根据整型主键查找
db.First(&product, "code = ?", "F43") // 查找 code 字段值为 D43 的记录
//fmt.Println(product)
//4 更新 - 将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// Update - 更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F43"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 300, "Code": "F42"})
//5 Delete - 删除 product
db.Delete(&product, 1) // 软删除
}
只要使用了gorm.Model结构体继承,DeletedAt DeletedAt `gorm:"index"` 字段
执行删除是其实是update语句,并没有真正的删除
var product Product
db.First(&product)
db.Model(&product).Updates(Product{Price: 200, Code: ""}) // 仅更新非零值字段
// UPDATE `products` SET `updated_at`='2022-09-03 23:48:51.96',`price`=200 WHERE `products`.`deleted_at` IS NULL AND `id` = 2
// 可以看到Code字段不会更新,这是合理的,因为如果零值字段也更新,Product表中好多数据都会被更新为空
//表模型修改为
type Product struct {
gorm.Model
Code sql.NullString
Price uint
}
// 修改语句为
db.Model(&product).Updates(Product{Price: 200, Code: sql.NullString{"",true}})
//表模型修改为
type Product struct {
gorm.Model
Code *string
Price uint
}
// 修改语句为
var product Product
db.First(&product)
var empty = ""
db.Model(&product).Updates(Product{Price: 200, Code: &empty}) // 仅更新非零值字段
模型是标准的 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 允许您自定义配置它们
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 进行 CRUD 时拥有全部的权限,此外,GORM 允许您用标签控制字段级别的权限。这样您就可以让一个字段的权限是只读、只写、只创建、只更新或者被忽略
注意: 使用 GORM Migrator 创建表时,不会创建被忽略的字段
type User struct {
Name string `gorm:"<-:create"` // allow read and create
Name string `gorm:"<-:update"` // allow read and update
Name string `gorm:"<-"` // allow read and write (create and update)
Name string `gorm:"<-:false"` // allow read, disable write permission
Name string `gorm:"->"` // readonly (disable write permission unless it configured)
Name string `gorm:"->;<-:create"` // allow read and create
Name string `gorm:"->:false;<-:create"` // createonly (disabled read from db)
Name string `gorm:"-"` // ignore this field when write and read with struct
Name string `gorm:"-:all"` // ignore this field when write, read and migrate with struct
Name string `gorm:"-:migration"` // ignore this field when migrate with struct
}
GORM 约定使用 CreatedAt
、UpdatedAt
追踪创建/更新时间。如果您定义了这种字段,GORM 在创建、更新时会自动填充 当前时间
要使用不同名称的字段,您可以配置 autoCreateTime
、autoUpdateTime
标签
如果您想要保存 UNIX(毫/纳)秒时间戳,而不是 time,您只需简单地将 time.Time
修改为 int
即可
type User struct {
CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充
UpdatedAt int // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
Updated int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳填纳秒数充更新时间
Updated int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间
Created int64 `gorm:"autoCreateTime"` // 使用时间戳秒数填充创建时间
}
对于匿名字段,GORM 会将其字段包含在父结构体中,例如:
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
}
对于正常的结构体字段,你也可以通过标签 embedded
将其嵌入,例如:
type Author struct {
Name string
Email string
}
type Blog struct {
ID int
Author Author `gorm:"embedded"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}
并且,您可以使用标签 embeddedPrefix
来为 db 中的字段名添加前缀,例如:
type Blog struct {
ID int
Author Author `gorm:"embedded;embeddedPrefix:author_"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
AuthorName string
AuthorEmail string
Upvotes int32
}
声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase
风格
标签名 | 说明 |
---|---|
column | 指定 db 列名 |
type | 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null 、size , autoIncrement … 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer | 指定如何将数据序列化和反序列化到数据库中的序列化程序,如: serializer:json/gob/unixtime |
size | 指定列数据大小/长度, 如: size:256 |
primaryKey | 指定列作为主键 |
unique | 指定列作为unique |
default | 指定列的默认值 |
precision | 指定列的精度 |
scale | 指定列的比例 |
not null | 指定列不为空 |
autoIncrement | 指定列自增 |
autoIncrementIncrement | 自动递增步长,控制连续列值之间的间隔 |
embedded | 嵌入字段 |
embeddedPrefix | 嵌入嵌入字段的字段列名前缀 |
autoCreateTime | 跟踪当前时间创建时,对于’int’字段,它将跟踪unix秒,使用值’nano/'milli 跟踪unix nano/milli秒,如: autoCreateTime:nano |
autoUpdateTime | 在创建/更新时跟踪当前时间,对于’int’字段,它将跟踪unix秒,使用值’nano/'milli 跟踪unix nano/milli秒, 如: autoUpdateTime:milli |
index | 使用选项创建索引,对多个字段使用相同的名称创建复合索引, 详情参照 Indexes |
uniqueIndex | 与’index’相同,但创建唯一索引 |
check | 创建检查约束, 如: check:age > 13 , 参照 Constraints |
<- | 设置字段的写入权限, <-:create 仅创建字段, <-:update 仅更新字段, <-:false 没有写权限, <- 创建和更新权限 |
-> | 设置字段读权限, ->:false 没有读权限 |
- | 忽略该字段, - 没有读写权限, -:migration 没有迁移权限, -:all 没有 read/write/migrate 权限 |
comment | 迁移时为字段添加注释 |
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
type User struct {
UserId uint `gorm:"primaryKey"`
Name string `gorm:"column:user_name;type:varchar(60)"`
Gender uint `gorm:"index"`
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err) // 如果数据库不存在会报错
}
db.AutoMigrate(&User{})
// 如果表之前存在会修改,但是只会修改之前存在的字段的属性,无法识别删除字段
}
我们尽量按照官方文档来看,网上教程都是老版本居多,有时候误差还挺大的
package main
import (
"database/sql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
type User struct {
gorm.Model
Name string
Age uint8
Birthday time.Time
}
func main() {
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&User{})
}
user := User{Name: "lxx", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
fmt.Println(user.ID) // 返回插入数据的主键
fmt.Println(result.Error) // 返回 error
fmt.Println(result.RowsAffected) // 返回插入记录的条数
创建记录并更新给出的字段。
user := User{Name: "json", Age: 19, Birthday: time.Now()}
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("json", 19, "2022-09-04 11:05:21.775")
创建一个记录且一同忽略传递给略去的字段值。
user := User{Name: "dlrb", Age: 20, Birthday: time.Now()}
db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`updated_at`,`deleted_at`,`birthday`) VALUES ('2022-09-04 18:29:07.531',NULL,'2022-09-04 18:29:07.53')
要有效地插入大量记录,请将一个 slice
传递给 Create
方法。 GORM 将生成单独一条SQL语句来插入所有数据,并回填主键的值,钩子方法也会被调用。
t,_:=time.Parse("2006-01-02","1998-07-12")
var users = []User{{Name: "彭于晏",Birthday: t}, {Name: "llnb",Birthday: t}, {Name: "古天乐",Birthday: t}}
db.Create(&users) // create 不仅可以放对象指针,也能放切片的指针。批量创建
for _, user := range users {
fmt.Println(user.ID)
}
因为Mysql5.7版本及以上版本的datetime值不能为'0000-00-00 00:00:00',
//处理方法:
修改mysql.ini
在[mysqld]添加一项:
sql_mode=NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO
使用 CreateInBatches
分批创建时,你可以指定每批的数量,例如:
var users [20]User
t, _ := time.Parse("2006-01-02", "1999-07-12")
for i, _ := range users {
users[i].Name = fmt.Sprintf("lxx__%d号", i)
users[i].Birthday = t
}
fmt.Println(users)
db.CreateInBatches(&users,10)
Upsert 和 Create With Associations 也支持批量插入
注意 使用
CreateBatchSize
选项初始化 GORM 时,所有的创建& 关联INSERT
都将遵循该选项
// 关联插入
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
CreateBatchSize: 1000,
})
db := db.Session(&gorm.Session{CreateBatchSize: 1000})
users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...}
db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)
GORM 允许用户定义的钩子有 BeforeSave
, BeforeCreate
, AfterSave
, AfterCreate
创建记录时将调用这些钩子方法,请参考 Hooks 中关于生命周期的详细信息
// main中
db.Create(&User{
Name: "lxx_nb",
Birthday: time.Now(),
})
// 插入之前的钩子函数
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
fmt.Println("User被插入了")
return
}
如果您想跳过 钩子
方法,您可以使用 SkipHooks
会话模式,例如:
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)
GORM 支持根据 map[string]interface{}
和 []map[string]interface{}{}
创建记录,例如:
// 此处可以不传Birthday因为它是null的
db.Model(&User{}).Create(map[string]interface{}{
"Name": "lxx_001", "Age": 18,
})
// batch insert from `[]map[string]interface{}{}`
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "lxx_002", "Age": 18},
{"Name": "lxx_003", "Age": 20},
})
注意: 根据 map 创建记录时,association(外键查询) 不会被调用,且主键也不会自动填充
GORM 允许使用 SQL 表达式插入数据,有两种方法实现这个目标。根据 map[string]interface{}
或 自定义数据类型 创建,例如:
// 通过 map 创建记录
db.Model(User{}).Create(map[string]interface{}{
"Name": "jinzhu",
"Location": clause.Expr{SQL: "ST_PointFromText(?)", Vars: []interface{}{"POINT(100 100)"}},
})
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"));
// 通过自定义类型创建记录
type Location struct {
X, Y int
}
// Scan 方法实现了 sql.Scanner 接口
func (loc *Location) Scan(v interface{}) error {
// Scan a value into struct from database driver
}
func (loc Location) GormDataType() string {
return "geometry"
}
func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
}
}
type User struct {
Name string
Location Location
}
db.Create(&User{
Name: "jinzhu",
Location: Location{X: 100, Y: 100},
})
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))
创建关联数据时,如果关联值是非零值,这些关联会被 upsert,且它们的 Hook
方法也会被调用
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
type User struct {
gorm.Model
Name string
CreditCard CreditCard
}
db.Create(&User{
Name: "jinzhu",
CreditCard: CreditCard{Number: "411111111111"}
})
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...
您也可以通过 Select
、 Omit
跳过关联保存,例如:
db.Omit("CreditCard").Create(&user)
// 跳过所有关联
db.Omit(clause.Associations).Create(&user)
您可以通过标签 default
为字段定义默认值,如:
type User struct {
ID int64
Name string `gorm:"default:galeone"`
Age int64 `gorm:"default:18"`
}
插入记录到数据库时,默认值 会被用于 填充值为 零值 的字段
注意 对于声明了默认值的字段,像
0
、''
、false
等零值是不会保存到数据库。您需要使用指针类型或 Scanner/Valuer 来避免这个问题,例如:
type User struct {
gorm.Model
Name string
Age *int `gorm:"default:18"`
Active sql.NullBool `gorm:"default:true"`
}
注意 若要数据库有默认、虚拟/生成的值,你必须为字段设置
default
标签。若要在迁移时跳过默认值定义,你可以使用default:(-)
,例如:
type User struct {
ID string `gorm:"default:uuid_generate_v3()"` // db func
FirstName string
LastName string
Age uint8
FullName string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"`
}
使用虚拟/生成的值时,你可能需要禁用它的创建、更新权限,查看 字段级权限 获取详情
GORM 为不同数据库提供了兼容的 Upsert 支持
有时候插入数据,报主键冲突,有upsert 既可以更新数据,又可以插入数据,来解决这个问题,如果主键存在就更新
import "gorm.io/gorm/clause"
// 在冲突时,什么都不做
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)
// 在`id`冲突时,将列更新为默认值
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"role": "user"}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL
// 使用SQL语句
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"count": gorm.Expr("GREATEST(count, VALUES(count))")}),
}).Create(&users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));
// 在`id`冲突时,将列更新为新值
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age=VALUES(age); MySQL
// 在冲突时,更新除主键以外的所有列到新值----》这个用的多
db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&users)
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;
您还可以查看 高级查询 中的 FirstOrInit
、FirstOrCreate
查看 原生 SQL 及构造器 获取更多细节
GORM 提供了 First
、Take
、Last
方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1
条件,且没有找到记录时,它会返回 ErrRecordNotFound
错误
var user User
// 获取第一条记录(主键升序)
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;
fmt.Println(user)
result := db.First(&user)
fmt.Println(result.RowsAffected) // 返回找到的记录数
fmt.Println(result.Error) // returns error or nil
// 检查 返回的错误是否是没找到记录的错误 ErrRecordNotFound 错误
fmt.Println(errors.Is(result.Error, gorm.ErrRecordNotFound))
如果你想避免
ErrRecordNotFound
错误,你可以使用Find
,比如db.Limit(1).Find(&user)
,Find
方法可以接受struct和slice的数据。
First
和 Last
会根据主键排序,分别查询第一条和最后一条记录。 只有在目标 struct 是指针或者通过 db.Model()
指定 model 时,该方法才有效。 此外,如果相关 model 没有定义主键,那么将按 model 的第一个字段进行排序。 例如:
var user User
var users []User
// works because destination struct is passed in
// 因为user自动和User结构体关联了
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// works because model is specified using `db.Model()`
// 使用map,需要指定跟那个表关联
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// doesn't work-->没指定Model,无效
result := map[string]interface{}{}
db.Table("users").First(&result)
// works with Take,使用Take就生效
result := map[string]interface{}{}
db.Table("users").Take(&result)
// no primary key defined, results will be ordered by first field (i.e., `Code`)
// 没有指定主键,就以第一个字段排序
type Language struct {
Code string
Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
如果主键是数字类型,您可以使用 内联条件 来检索对象。 传入字符串参数时,需要特别注意 SQL 注入问题,查看 安全 获取详情.
var user User
var users []User
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);
fmt.Println(user)
fmt.Println(users)
如果主键是字符串(例如像 uuid),查询将被写成这样:
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
当目标对象有主值时,主键将用于构建条件,例如:
//当目标对象具有主键值时,主键将用于构建条件
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;
var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;
var users []User
// Get all records
result := db.Find(&users)
// SELECT * FROM users;
fmt.Println(result.RowsAffected) // returns found records count, equals `len(users)`
fmt.Println(result.Error) // returns error
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' 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
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
注意 使用 struct 查询时,GORM 只会查询非零字段,这意味着如果您的字段的值为
0
、''
、false
或其他 [零值](https:// tour.golang.org/basics/12),它不会用于构建查询条件,例如:
// 当使用结构体作为查询条件,gorm只会查询非0字段,如果字段是`0`, `''`, `false` or other zero values,该字段不会被用于构建查询条件
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";
要在查询条件中包含零值,您可以使用映射,它将包含所有键值作为查询条件,例如:
// 如果想要在查询中包含0的字段,可以使用map来做
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
更多详情请参见指定结构搜索字段。
使用 struct 进行搜索时,您可以通过将相关字段名称或 dbname 传递给 Where()
来指定要在查询条件中使用的 struct 中的哪些特定值,例如:
//在使用struct进行搜索时,可以通过将相关字段名或数据库名传递给`Where(),来指定在查询条件中使用struct中的哪些特定值`
db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;
查询条件可以以类似于Where的方式内联到’First’和’Find’等方法中
// 如果是非整形主键,根据主键获取记录
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';
// 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条件,工作原理同Where
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;
// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
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);
对于更复杂的 SQL 查询。另请参考高级查询中的分组条件。
`Select``允许您指定要从数据库检索的字段。否则,GORM将默认选择所有字段
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;
另请查看 Smart Select Fields
指定从数据库检索记录时的顺序
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.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)
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;
// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
分页器的制作方法参考分页器
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` LIMIT 1
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()
defer rows.Close()
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()
defer rows.Close()
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)
从模型中选择不同的值, 去重
db.Distinct("name", "age").Order("name, age desc").Find(&results)
Distinct
works with Pluck
and Count
too
Specify Joins conditions-指定连接条件
type result struct {
Name string
Email string
}
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)
// multiple joins with parameter
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
可以使用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`;
加入条件
db.Joins("Company", DB.Where(&Company{Alive: true})).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` AND `Company`.`alive` = true;
更多详情请参考预加载(Eager Loading)
还可以使用 Joins
来链接关联表
type User struct {
Id int
Age int
}
type Order struct {
UserId int
FinishedAt *time.Time
}
query := db.Table("order").Select("MAX(order.finished_at) as latest").Joins("left join user user on order.user_id = user.id").Where("user.age > ?", 18).Group("order.user_id")
db.Model(&Order{}).Joins("join (?) q on order.finished_at = q.latest", query).Scan(&results)
// SELECT `order`.`user_id`,`order`.`finished_at` FROM `order` join (SELECT MAX(order.finished_at) as latest FROM `order` left join user user on order.user_id = user.id WHERE user.age > 18 GROUP BY `order`.`user_id`) q on order.finished_at = q.latest
将结果扫描到结构体中的工作方式,与Find
类似
type Result struct {
Name string
Age int
}
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
// Raw SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
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
注意
QueryFields
模式会根据当前 model 的所有字段名称进行 select。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
QueryFields: true,
})
db.Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users` // 带上这个选项
// Session Mode
db.Session(&gorm.Session{QueryFields: true}).Find(&user)
// SELECT `users`.`name`, `users`.`age`, ... FROM `users`
GORM 支持多种类型的锁,例如:
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR UPDATE
db.Clauses(clause.Locking{
Strength: "SHARE",
Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`
db.Clauses(clause.Locking{
Strength: "UPDATE",
Options: "NOWAIT",
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
查看 原生 SQL 及构造器 获取详情
子查询可以嵌套在查询中,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%")
GORM 允许您在 Table
方法中通过 FROM 子句使用子查询,例如:
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18}).Find(&User{})
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18
subQuery1 := db.Model(&User{}).Select("name")
subQuery2 := db.Model(&Pet{}).Select("name")
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
使用 Group 条件可以更轻松的编写复杂 SQL
db.Where(
db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
).Or(
db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
).Find(&Pizza{}).Statement
// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")
带多个列的 In 查询
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
GORM 支持 sql.NamedArg
和 map[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": "jinzhu"}).First(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu" ORDER BY `users`.`id` LIMIT 1
查看 原生 SQL 及构造器 获取详情
GORM 允许扫描结果至 map[string]interface{}
或 []map[string]interface{}
,此时别忘了指定 Model
或 Table
,例如:
result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)
var results []map[string]interface{}
db.Table("users").Find(&results)
获取第一条匹配的记录,或者根据给定的条件初始化一个实例(仅支持 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}
获取第一个匹配记录或在给定条件下创建一个新记录(仅适用于结构、映射条件),RowsAffected
返回创建/更新记录的计数
// User not found, create a new record with give conditions
result := db.FirstOrCreate(&user, User{Name: "non_existing"})
// INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// result.RowsAffected // => 0
// Found user with `name` = `jinzhu`
result := db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)
// user -> User{ID: 111, Name: "jinzhu", "Age": 18}
// result.RowsAffected // => 0
如果没有找到记录,可以使用包含更多的属性的结构体创建记录,Attrs
不会被用于生成查询 SQL 。
// 未找到 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}
不管是否找到记录,Assign
都会将属性赋值给 struct,并将结果写回数据库
// 未找到 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}
优化器提示用于控制查询优化器选择某个查询执行计划,GORM 通过 gorm.io/hints
提供支持,例如:
import "gorm.io/hints"
db.Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find(&User{})
// SELECT * /*+ MAX_EXECUTION_TIME(10000) */ FROM `users`
索引提示允许传递索引提示到数据库,以防查询计划器出现混乱。
import "gorm.io/hints"
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM `users` USE INDEX (`idx_user_name`)
db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})
// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"
参考 优化器提示、索引、备注 获取详情
GORM 支持通过行进行迭代
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()
for rows.Next() {
var user User
// ScanRows 方法用于将一行记录扫描至结构体
db.ScanRows(rows, &user)
// 业务逻辑...
}
用于批量查询并处理记录
// 每次批量处理 100 条
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// 批量处理找到的记录
}
tx.Save(&results)
tx.RowsAffected // 本次批量操作影响的记录数
batch // Batch 1, 2, 3
// 如果返回错误会终止后续批量操作
return nil
})
result.Error // returned error
result.RowsAffected // 整个批量操作影响的记录数
对于查询操作,GORM 支持 AfterFind
钩子,查询记录后会调用它,详情请参考 钩子
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.Role == "" {
u.Role = "user"
}
return
}
Pluck 用于从数据库查询单个列,并将结果扫描到切片。如果您想要查询多列,您应该使用 Select
和 Scan
var ages []int64
db.Model(&users).Pluck("age", &ages)
var names []string
db.Model(&User{}).Pluck("name", &names)
db.Table("deleted_users").Pluck("name", &names)
// Distinct Pluck
db.Model(&User{}).Distinct().Pluck("Name", &names)
// SELECT DISTINCT `name` FROM `users`
// 超过一列的查询,应该使用 `Scan` 或者 `Find`,例如:
db.Select("name", "age").Scan(&users)
db.Select("name", "age").Find(&users)
Scopes
允许你指定常用的查询,可以在调用方法时引用这些查询
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.Where("status IN (?)", status)
}
}
db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查找所有金额大于 1000 的信用卡订单
db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// 查找所有金额大于 1000 的货到付款订单
db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// 查找所有金额大于 1000 且已付款或已发货的订单
查看 Scopes 获取详情
Count 用于获取匹配的记录数
var count int64
db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)
db.Table("deleted_users").Count(&count)
// SELECT count(1) FROM deleted_users;
// Count with Distinct
db.Model(&User{}).Distinct("name").Count(&count)
// SELECT COUNT(DISTINCT(`name`)) FROM `users`
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SELECT count(distinct(name)) FROM deleted_users
// Count with Group
users := []User{
{Name: "name1"},
{Name: "name2"},
{Name: "name3"},
{Name: "name3"},
}
db.Model(&User{}).Group("name").Count(&count)
count // => 3
// 表模型变成
type User struct {
gorm.Model
Name string
Age uint8
Birthday time.Time
Active bool
}
// 删除表,重新迁移
db.AutoMigrate(&User{})
// 插入记录
var user =User{Name: "lxx",Age: 18,Birthday: time.Now(),Active: true}
db.Create(&user)
Save
会保存所有的字段,即使字段是零值
var user User
db.First(&user)
user.Name = "lxx"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
当使用 Update
更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause
错误,查看 Block Global Updates 获取详情。当使用了 Model
方法,且该对象主键有值,该值会被用于构建条件,例如:
// 条件更新(如果有多条,全更新)
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
// User 的 ID 是 `1`
var user User
db.First(&user)
db.Model(&user).Update("name", "lxx_nb")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
// 根据条件和 model 的值进行更新
var user User
db.First(&user)
db.Model(&user).Where("active = ?", true).Update("name", "lxx_hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
Updates
方法支持 struct
和 map[string]interface{}
参数。当使用 struct
更新时,默认情况下,GORM 只会更新非零值的字段
// 根据 `struct` 更新属性,只会更新非零值的字段
var user User
db.First(&user)
db.Model(&user).Updates(User{Name: "hello", Age: 99, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
// 根据 `map` 更新属性
var user User
db.First(&user)
db.Model(&user).Updates(map[string]interface{}{"name": "lxx_01", "age": 88, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
注意 当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用
Select
更新选定字段,或使用map
来完成更新操作
如果您想要在更新时选定、忽略某些字段,您可以使用 Select
、Omit
// 使用 Map 进行 Select
// User's ID is `111`:
var user User
db.First(&user)
// 只更新name
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "lxx_002", "age": 66, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
var user User
db.First(&user)
// 除了name都更新
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": true})
}
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// 使用 Struct 进行 Select(会 select 零值的字段),零值字段也会更新
// 注意 user的id不能为0
var user User
db.First(&user)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// Select 所有字段(查询包括零值字段的所有字段)
var user User
db.First(&user)
fmt.Println(user)
db.Model(&user).Select("*").Updates(User{Name: "lxx_004", Birthday:time.Now(), Age: 33,Active: false, Model:gorm.Model{ID:3,CreatedAt: time.Now(),UpdatedAt: time.Now()}})
// Select 除 "CreatedAt","birthday"外的所有字段(包括零值字段的所有字段),注意id会变成0
db.Model(&user).Select("*").Omit("CreatedAt","birthday").Updates(User{Name: "jinzhu", Age: 0})
对于更新操作,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
}
// 测试
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
fmt.Println("修改了")
return
}
如果您尚未通过 Model
指定记录的主键,则 GORM 会执行批量更新
// 根据 struct 更新--->批量更新不能用Hook
db.Model(User{}).Where("active = ?", true).Updates(User{Name: "hello", Age: 18})
// UPDATE `users` SET `updated_at`='2022-05-05 01:01:53.001',`name`='hello',`age`=18 WHERE active = true AND `users`.`deleted_at` IS NULL
// 根据 map 更新
db.Table("users").Where("id IN ?", []int{1, 2}).Updates(map[string]interface{}{"name": "lxx_009", "age": 99})
// UPDATE `users` SET `age`=99,`name`='lxx_009' WHERE id IN (1,2)
如果在没有任何条件的情况下执行批量更新,默认情况下,GORM 不会执行该操作,并返回 ErrMissingWhereClause
错误
对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate
模式,例如:
db.Model(&User{}).Update("name", "jinzhu").Error //报错 gorm.ErrMissingWhereClause
db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu") //可以
// UPDATE users SET `name` = "jinzhu" WHERE 1=1
// 方式一:直接执行原生sql可以全局更新
db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"
// 方式二:使用session
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "lxx_010")
// UPDATE users SET `name` = "lxx_010"
获取受更新影响的行数
// 通过 `RowsAffected` 得到更新的记录数
result := db.Model(User{}).Where("active = ?", true).Updates(User{Name: "hello", Age: 18})
fmt.Println(result.RowsAffected) // 更新的记录数
fmt.Println(result.Error) // 错误
GORM 允许使用 SQL 表达式更新列,例如:
// user 的 ID 是 `1`
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`='2022-05-05 01:11:16.242' WHERE `users`.`deleted_at` IS NULL AND `id` = 1
var user User
db.First(&user)
db.Model(&user).Updates(map[string]interface{}{"age": gorm.Expr("age - ? + ?", 2, 100)})
// UPDATE `users` SET `age`=age - 2 + 100,`updated_at`='2022-05-05 01:13:13.302' WHERE `users`.`deleted_at` IS NULL AND `id` = 1
db.First(&user)
db.Model(&user).UpdateColumn("age", gorm.Expr("age - ?", 10))
// UPDATE `users` SET `age`=age - 10 WHERE `users`.`deleted_at` IS NULL AND `id` = 1
var user User
db.First(&user)
db.Model(&user).Where("age > 100").UpdateColumn("age", gorm.Expr("age - ?", 100))
// UPDATE `users` SET `age`=age - 100 WHERE age > 100 AND `users`.`deleted_at` IS NULL AND `id` = 1
并且 GORM 也允许使用 SQL 表达式、自定义数据类型的 Context Valuer 来更新,例如:
// 根据自定义数据类型创建
type Location struct {
X, Y int
}
func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
}
}
db.Model(&User{ID: 1}).Updates(User{
Name: "jinzhu",
Location: Location{X: 100, Y: 100},
})
// UPDATE `user_with_points` SET `name`="jinzhu",`location`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1
使用子查询更新表
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 = ?", "jinzhu").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
//UPDATE users as u SET `company_name`=(SELECT name FROM companies as c WHERE c.id = u.company_id) WHERE name = 'jinzhu'
db.Table("users as u").Where("name = ?", "jinzhu").Updates(map[string]interface{}{"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})
//UPDATE users as u SET `company_name`=(SELECT name FROM companies as c WHERE c.id = u.company_id) WHERE name = 'jinzhu'
如果您想在更新时跳过 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 提供了 Changed
方法,它可以被用在 Before Update Hook 里,它会返回字段是否有变更的布尔值
Changed
方法只能与 Update
、Updates
方法一起使用,并且它只是检查 Model 对象字段的值与 Update
、Updates
的值是否相等,如果值有变更,且字段没有被忽略,则返回 true
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
// 如果 age 字段有变更,不允许,直接报错
if tx.Statement.Changed("age") {
fmt.Println("xx")
return errors.New("age not allowed to change")
}
// 如果 Name 字段有变更,把年龄改为18
if tx.Statement.Changed("Name") {
tx.Statement.SetColumn("age", 18)
}
// 如果任意字段有变更,修改CreatedAt时间
if tx.Statement.Changed() {
tx.Statement.SetColumn("CreatedAt", time.Now())
}
return nil
}
//修改名字,把age 设为18
db.Model(&User{Model:gorm.Model{ID: 1} , Name: "lxx"}).Updates(map[string]interface{}{"name": "lxx_nb"})
// 名字修改,但是名字其实并没有变,所以年龄不会被修改
db.Model(&User{Model:gorm.Model{ID: 1}, Name: "lxx_nb"}).Updates(map[string]interface{}{"name": "lxx_nb"})
//修改名字,把age 设为18
db.Model(&User{Model:gorm.Model{ID: 1}, Name: "lxx_nb"}).Updates(User{Name: "jinzhu2"})
//名字修改,但是名字其实并没有变,所以年龄不会被修改
db.Model(&User{Model:gorm.Model{ID: 1}, Name: "jinzhu2"}).Updates(User{Name: "jinzhu2"})
// 不允许修改age
db.Model(&User{}).Where("id=?",1).Updates(map[string]interface{}{"age": 100})
//任意字段有变更,更新CreatedAt时间
db.Model(&User{}).Where("id=?",1).Updates(map[string]interface{}{"active": false})
若要在 Before 钩子中改变要更新的值,如果它是一个完整的更新,可以使用 Save
;否则,应该使用 SetColumn
,例如:
func (user *User) BeforeSave(tx *gorm.DB) (err error) {
if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
tx.Statement.SetColumn("EncryptedPassword", pw)
}
if tx.Statement.Changed("Code") {
user.Age += 20
tx.Statement.SetColumn("Age", user.Age)
}
}
db.Model(&user).Update("Name", "jinzhu")
var user User
db.First(&user)
db.Model(&user).Update("Name", "lxx008")
// 只要更新名字,就把名字加密
func (user *User) BeforeSave(tx *gorm.DB) (err error) {
if genName, err := bcrypt.GenerateFromPassword([]byte(user.Name), 0); err == nil {
tx.Statement.SetColumn("name", genName)
}
return nil
}
删除一条记录时,删除对象需要指定主键,否则会触发 批量 Delete,例如:
// user 的 ID 是 `1`--->软删除
var user User
db.First(&user)
db.Delete(&user)
// UPDATE `users` SET `deleted_at`='2022-05-05 02:05:16.7' WHERE `users`.`id` = 1 AND `users`.`deleted_at` IS NULL
// 带额外条件的删除
var user User
db.First(&user)
db.Where("name = ?", "hello").Delete(&user)
//db.Where("name = ?", "hello").Delete(&User{})
// UPDATE `users` SET `deleted_at`='2022-05-05 02:08:30.857' WHERE name = 'hello' AND `users`.`deleted_at` IS NULL
GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串——译者注)。查看 查询-内联条件(Query Inline Conditions) 了解详情。
// 先插入
var user =User{Name: "lxx001",Age: 99,Birthday: time.Now()}
db.Create(&user)
db.Delete(&User{}, 3)
db.Delete(&User{}, "10")
b.Delete(&User{}, []int{1,2,3})
对于删除操作,GORM 支持 BeforeDelete
、AfterDelete
Hook,在删除记录时会调用这些方法,查看 Hook 获取详情
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to delete")
}
return
}
如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录
db.Where("name LIKE ?", "%lxx%").Delete(&User{})
db.Delete(&User{}, "name LIKE ?", "%lxx%")
如果在没有任何条件的情况下执行批量删除,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
返回被删除的数据,仅适用于支持 Returning 的数据库,例如:
// 返回所有列
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}}
如果您的模型包含了一个 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
}
您可以使用 Unscoped
找到被软删除的记录
var users []User
db.Unscoped().Where("age = 99").Find(&users)
fmt.Println(users)
您也可以使用 Unscoped
永久删除匹配的记录
var users []User
db.Unscoped().Where("age = 99").Find(&users)
db.Unscoped().Delete(&users)
将 unix 时间戳作为 delete flag
// go get -u gorm.io/plugin/soft_delete
//db.AutoMigrate(&User2{})
import "gorm.io/plugin/soft_delete"
type User2 struct {
ID uint
Name string
DeletedAt soft_delete.DeletedAt
}
// 创建
var user2 User2=User2{Name: "lxx"}
db.Create(&user2)
// 删除
db.Delete(&user2)
// 查询
SELECT * FROM users WHERE deleted_at = 0;
// 删除
UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;
INFO 在配合 unique 字段使用软删除时,您需要使用这个基于 unix 时间戳的
DeletedAt
字段创建一个复合索引,例如:import "gorm.io/plugin/soft_delete" type User struct { ID uint Name string `gorm:"uniqueIndex:udx_name"` DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"` }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
使用 1
/ 0
作为 delete flag
import "gorm.io/plugin/soft_delete"
type User struct {
ID uint
Name string
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}
// 查询
SELECT * FROM users WHERE is_del = 0;
// 删除
UPDATE users SET is_del = 1 WHERE ID = 1;
原生查询 SQL 和 Scan
type User3 struct {
ID int
Name string
Age int
}
// 创建表
db.AutoMigrate(&User3{})
// 插入数据
var user User3
db.Raw("SELECT id, name, age FROM user3 WHERE name = ?", "lxx").Scan(&user)
fmt.Println(user)
var age int
db.Raw("SELECT SUM(age) FROM user3 WHERE name like ?", "%lxx%").Scan(&age)
fmt.Println(age)
var users []User3
db.Raw("SELECT * FROM user3 WHERE id > ?", 0).Scan(&users)
fmt.Println(users)
// mysql 不支持
var users []User3
db.Raw("UPDATE user3 SET name = ? WHERE age = ? RETURNING id, name", "lxx", 12).Scan(&users)
fmt.Println(users)
Exec
原生 SQL
db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
// Exec with SQL Expression
db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
注意 GORM 允许缓存预编译 SQL 语句来提高性能,查看 性能 获取详情
GORM 支持 sql.NamedArg
、map[string]interface{}{}
或 struct 形式的命名参数,例如:
// 测试
var user []User3
db.Where("name = @name OR age = @age", sql.Named("name", "lxx"),sql.Named("age", "12")).Find(&user)
fmt.Println(user)
var users []User3
db.Where("name = @name OR age = @age", sql.Named("name", "lxx"),sql.Named("age", "12")).Find(&users)
fmt.Println(users)
// 其他一样
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
// 原生 SQL 及命名参数
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"
type NamedArgument struct {
Name string
Name2 string
}
db.Raw("SELECT * FROM users WHERE (name1 = @Name AND name3 = @Name) AND name2 = @Name2",
NamedArgument{Name: "jinzhu", Name2: "jinzhu2"}).Find(&user)
// SELECT * FROM users WHERE (name1 = "jinzhu" AND name3 = "jinzhu") AND name2 = "jinzhu2"
在不执行的情况下生成 SQL
及其参数,可以用于准备或测试生成的 SQL,详情请参考 Session
var user User3
//session := db.Session(&gorm.Session{DryRun: true})
//session.First(&user, 1)
//fmt.Println(user)
stmt := db.Session(&gorm.Session{DryRun: true}).First(&user, 1).Statement
fmt.Println(stmt.SQL.String()) //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
fmt.Println(stmt.Vars) //=> []interface{}{1}
返回生成的 SQL
但不执行。
GORM使用 database/sql 的参数占位符来构建 SQL 语句,它会自动转义参数以避免 SQL 注入,但我们不保证生成 SQL 的安全,请只用于调试。
var users []User3
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Model(&User3{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&users)
})
fmt.Println(sql)
//SELECT * FROM `user3` WHERE id = 100 ORDER BY age desc LIMIT 10
Row
& Rows
获取 *sql.Row
结果
// 使用 GORM API 构建 SQL
var name string
var age int
row := db.Table("user3").Where("name = ?", "lxx").Select("name", "age").Row()
row.Scan(&name, &age)
fmt.Println(name)
fmt.Println(age)
// 使用原生 SQL
var name string
var age int
row := db.Raw("select name, age from user3 where name = ?", "lxx").Row()
row.Scan(&name, &age)
fmt.Println(name)
fmt.Println(age)
获取 *sql.Rows
结果
// 使用 GORM API 构建 SQL
var name string
var age int
rows, _ := db.Model(&User3{}).Where("name like ?", "%lxx%").Select("name, age").Rows()
defer rows.Close()
for rows.Next() {
rows.Scan(&name, &age)
fmt.Printf("name是:%s,age是:%d\n",name,age)
}
// 原生 SQL
var name string
var age int
rows, err := db.Raw("select name, age from user3 where name like ?", "%lxx%").Rows()
defer rows.Close()
for rows.Next() {
rows.Scan(&name, &age)
fmt.Printf("name是:%s,age是:%d\n",name,age)
}
转到 FindInBatches 获取如何在批量中查询和处理记录的信息, 转到 Group 条件 获取如何构建复杂 SQL 查询的信息
sql.Rows
扫描至 model使用 ScanRows
将一行记录扫描至 struct,例如:
rows, err := db.Model(&User3{}).Where("name like ?", "%lxx%").Select("name, age").Rows()
defer rows.Close()
var user User3
for rows.Next() {
// ScanRows 将一行扫描至 user
db.ScanRows(rows, &user)
fmt.Println(user)
}
在同一个 db tcp 连接中运行多个 SQL(不在事务中)
db.Connection(func(tx *gorm.DB) error {
tx.Exec("SET my.role = ?", "admin")
tx.First(&User{})
})
GORM 使用 SQL builder 在内部生成 SQL,对于每个操作,GORM 都会创建一个 *gorm.Statement
对象,所有 GORM API 都会为 Statement
添加/更改 Clause
,最后,GORM 基于这些子句生成 SQL
例如,当使用 First
查询时,它会将以下子句添加到 Statement
clause.Select{Columns: "*"}
clause.From{Tables: clause.CurrentTable}
clause.Limit{Limit: 1}
clause.OrderByColumn{
Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
}
然后 GORM 构建最终在 Query 回调中查询 SQL,例如:
Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR")
生成SQL:
SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
您可以定义自己的Clause
并与GORM一起使用,它需要实现Interface
查看 示例 以供参考
对于不同的数据库,子句可能会产生不同的 SQL,例如:
db.Offset(10).Limit(5).Find(&users)
// Generated for SQL Server
// SELECT * FROM "users" OFFSET 10 ROW FETCH NEXT 5 ROWS ONLY
// Generated for MySQL
// SELECT * FROM `users` LIMIT 5 OFFSET 10
支持是因为 GORM 允许数据库驱动注册 Clause Builder 替换默认的,以 Limit 为例
GORM 定义了 Many Clauses,并且一些子句提供了高级选项,可用于您的应用程序
尽管它们中的大多数很少使用,但如果您发现 GORM 公共 API 无法满足您的要求,不妨检查一下,例如:
db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&user)
// INSERT IGNORE INTO users (name,age...) VALUES ("jinzhu",18...);
GORM 提供接口 StatementModifier 允许您修改语句以符合您的要求,采取 [Hints](https://gorm.io /zh_CN/docs/hints.html) 为例
import "gorm.io/hints"
db.Clauses(hints.New("hint")).Find(&User{})
// SELECT * /*+ hint */ FROM `users`
belongs to
会与另一个模型建立了一对一的连接。 这种模型的每一个实例都“属于”另一个模型的一个实例。
例如,您的应用包含 user 和 company,并且每个 user 能且只能被分配给一个 company。下面的类型就表示这种关系。 注意,在 User
对象中,有一个和 Company
一样的 CompanyID
。 默认情况下, CompanyID
被隐含地用来在 User
和 Company
之间创建一个外键关系, 因此必须包含在 User
结构体中才能填充 Company
内部结构体。
// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {
gorm.Model
Name string
CompanyID int // 必须以 结构体名 + ID 的方式
Company Company
}
type Company struct {
ID int
Name string
}
db.AutoMigrate(&User{},&Company{})
请参阅 预加载 以了解内部结构的详细信息。
要定义一个 belongs to 关系,数据库的表中必须存在外键。默认情况下,外键的名字,使用拥有者的类型名称加上表的主键的字段名字
例如,定义一个User实体属于Company实体,那么外键的名字一般使用CompanyID。
GORM同时提供自定义外键名字的方式,如下例所示。
type User struct {
gorm.Model
Name string
CompanyRefer int // 外键改名字为CompanyRefer
Company Company `gorm:"foreignKey:CompanyRefer"`
// 使用 CompanyRefer 作为外键
}
type Company struct {
ID int
Name string
}
对于 belongs to 关系,GORM 通常使用数据库表,主表(拥有者)的主键值作为外键参考。 正如上面的例子,我们使用主表Company中的主键字段ID作为外键的参考值。
如果在Company实体中设置了User实体,那么GORM会自动把Company中的ID属性保存到User的CompanyID属性中。
同样的,您也可以使用标签 references
来更改它,例如:
type User struct {
gorm.Model
Name string
CompanyCode int
Company Company `gorm:"foreignKey:CompanyCode;references:Code"` // 指定外键字段为CompanyCode,指定与公司表中Code字段关联
}
type Company struct {
ID int
Code int `gorm:"primarykey"`
Name string
}
点击 关联模式 链接获取 belongs to 相关的用法
GORM允许通过使用Preload
或者Joins
来主动加载实体的关联关系,具体内容请参考,预加载(主动加载)
你可以通过OnUpdate
, OnDelete
配置标签来增加关联关系的级联操作,如下面的例子,通过GORM可以完成用户和公司的级联更新和级联删除操作:
type User struct {
gorm.Model
Name string
Age int
UserDetailID int
UserDetail UserDetail `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` // 级联更新,删除时置空
}
type UserDetail struct {
ID int
Addr string
}
// Navicat的设计表中可以查看
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
type User struct {
gorm.Model
Name string
CompanyID int // 数据库中字段
Company Company // 不在数据库生成,只用来快速查询用
}
type Company struct {
ID int
Name string
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err)
}
//db.AutoMigrate(&User{}) // Company也会被一起创建好,并设置了外键
// 方式一:插入数据,两个表都会被插入
//db.Create(&User{
// Name:"lxx",
// Company: Company{
// ID: 1,
// Name: "抖音",
// },
//})
// 方式一:使用CompanyID插入
//db.Create(&User{
// Name:"lxx2",
// CompanyID: 1,
//})
// 方式三:使用Company插入,但ID存在了
db.Create(&User{
Name:"lxx3",
Company: Company{
ID:1,
},
})
}
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
type User struct {
gorm.Model
Name string
CompanyID int // 数据库中字段 默认必须为 结构体名+ID
Company Company // 不在数据库生成,只用来快速查询用
}
type Company struct {
ID int
Name string
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err)
}
// 1 普通查询
var user User
//db.First(&user)
//fmt.Println(user)
//fmt.Println(user.Company) // 空的,没有查出来,也就是默认并不会二次查询
// 3 Preload预加载
//db.Preload("Company").First(&user)
//fmt.Println(user.Company)
// 4 Joins链表查
db.Joins("Company").First(&user)
fmt.Println(user.Company)
}
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
type User struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignkey:UserID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err)
}
// 1 创建表
//db.AutoMigrate(&User{},&CreditCard{})
//2 插入数据
//var user=User{}
//db.Create(&user) // 插入user
//db.Create(&CreditCard{ // 插入CreditCard
// Number:"1001",
// UserID:user.ID,
//})
//db.Create(&CreditCard{ // 插入CreditCard
// Number:"1002",
// UserID:user.ID,
//})
// 3 查询
var user User
db.Preload("CreditCards").First(&user)
fmt.Println(user.CreditCards)
}
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_languages;"`
}
type Language struct {
gorm.Model
Name string
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err)
}
// 1 创建表
//db.AutoMigrate(&User{}) //language表和中间表会自动创建
// 2 插入数据
//var lan =[]Language{}
//lan=append(lan,Language{
// Name: "Go语言",
//})
//lan=append(lan,Language{
// Name: "Python语言",
//})
//db.Create(&User{
// Languages:lan,
//})
// 3 Preload查询数据
//var user User
//db.Preload("Languages").First(&user)
//fmt.Println(user.Languages)
// 4 没有使用Preload已经拿到User,再想获取Languages
var user User
db.First(&user)
fmt.Println(user.Languages)
var lan []Language
db.Model(&user).Association("Languages").Find(&lan)
fmt.Println(lan)
}
// 给表结构体绑定方法即可
func (user *User) TableName ()string {
return "lxx_user"
}
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_", // table name prefix, table for `User` would be `t_users`
SingularTable: true, // use singular table name, table for `User` would be `user` with this option enabled
NoLowerCase: true, // skip the snake_casing of names
NameReplacer: strings.NewReplacer("CID", "Cid"), // use name replacer to change struct/field name before convert it to db name
},
})
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"log"
"os"
"time"
)
type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_languages;"`
}
//func (user *User) TableName ()string {
// return "lxx_user"
//}
type Language struct {
gorm.Model
Name string
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
// 跟上面的方式冲突,优先用上面方式
NamingStrategy: schema.NamingStrategy{
TablePrefix: "lxx_",
},
})
if err != nil {
panic(err)
}
// 1 创建表
db.AutoMigrate(&User{})
}
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"log"
"os"
"time"
)
type User struct {
ID uint
Name string
AddTime time.Time
}
func main() {
// 日志配置
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别为info
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 彩色打印
},
)
dsn := "root:123@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
NamingStrategy: schema.NamingStrategy{
TablePrefix: "lxx_",
},
})
if err != nil {
panic(err)
}
// 1 创建表
//db.AutoMigrate(&User{})
// 报错
//db.Create(&User{
// Name: "lxx",
//})
// 可以
//db.Create(&User{
// Name: "lxx",
// AddTime: time.Now(),
//})
// 写了钩子后,不用手动写时间
db.Create(&User{
Name: "lxx2",
})
}
// 编写钩子
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.AddTime=time.Now()
return
}