golangORM框架gorm详解(超详细)
作者:mmseoamin日期:2023-12-11

ORM简介

对象关系映射模式(object relational Mapping)是为了解决面向对象和关系型数据库存在的互不匹配的问题,简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

gorm的安装:

go get -u gorm.io/gorm

go get -u gorm.io/driver/sqlite

快速入门:

package main
import (
 "gorm.io/gorm"
 "gorm.io/driver/mysql"
)
type Product struct {
 gorm.Model
 Code  string
 Price uint
}
func main() {
 dsn := "root:123456@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
 if err != nil {
   panic("failed to connect database")
}
 // 迁移 schema
 db.AutoMigrate(&Product{})
 // Create
 db.Create(&Product{Code: "D42", Price: 100})
 // Read
 var product Product
 db.First(&product, 1) // 根据整形主键查找
 db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
 // Update - 将 product 的 price 更新为 200
 db.Model(&product).Update("Price", 200)
 // Update - 更新多个字段
 db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
 db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
 // Delete - 删除 product
 db.Delete(&product, 1)
}

gorm声明模型

模型定义

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

eg:

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.model

gorm定义了一个gorm.Model结构体,其包含字段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"` // 允许读和创建
 Name string `gorm:"<-:update"` // 允许读和更新
 Name string `gorm:"<-"`        // 允许读和写(创建和更新)
 Name string `gorm:"<-:false"`  // 允许读,禁止写
 Name string `gorm:"->"`        // 只读(除非有自定义配置,否则禁止写)
 Name string `gorm:"->;<-:create"` // 允许读和写
 Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
 Name string `gorm:"-"`  // 通过 struct 读写会忽略该字段
}

创建|更新时间追踪(纳秒。毫秒,秒,time)

gorm约定使用createAt,UpdatedAt追踪创建|更新时间,如果您定义了这种字段,gorm在创建,更新是会自动填充当前时间

要是用不同名称的字段,您可以配置autoCreateTime,AutoUpdateTime标签

如果您想要保存unix秒时间戳,而不是time,您只需要简单的将time.Time修改为int 即可

type User struct {
 CreatedAt time.Time // Set to current time if it is zero on creating
 UpdatedAt int       // Set to current unix seconds on updating or if it is zero on creating
 Updated   int64 `gorm:"autoUpdateTime:nano"` // Use unix nano seconds as updating time
 Updated   int64 `gorm:"autoUpdateTime:milli"`// Use unix milli seconds as updating time
 Created   int64 `gorm:"autoCreateTime"`      // Use unix seconds as creating time
}

嵌入结构体

对于匿名字段 ,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
}

并且,您可以使用标签embededPrefix来为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风格

golangORM框架gorm详解(超详细),在这里插入图片描述,第1张

关联标签

gorm允许通过标签为关联配置外键,约束,many2mang表

gorm连接达到数据库

gorm官方支持的数据库类型有:mysql。postgreSQL,sqlserver

MYSQL:

import (
 "gorm.io/driver/mysql"
 "gorm.io/gorm"
)
func main() {
 // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
 dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

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允许通过DriverName选项自定义Mysql驱动,例如:

import (
 _ "example.com/my_mysql_driver"
 "gorm.io/gorm"
)
db, err := gorm.Open(mysql.New(mysql.Config{
 DriverName: "my_mysql_driver",
 DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local", // Data Source Name,参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name
}), &gorm.Config{})

现有的数据库连接

gorm允许通过一个现有的数据库连接来出初始化 *gorm.DB

import (
 "database/sql"
 "gorm.io/gorm"
)
sqlDB, err := sql.Open("mysql", "mydb_dsn")
gormDB, err := gorm.Open(mysql.New(mysql.Config{
 Conn: sqlDB,
}), &gorm.Config{})

gorm创建记录

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

用指定的字段创建记录:

创建记录并更新给出的字段。

db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

创建一个记录且同时忽略传递给略去的字段值:

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
批量插入

要有效的 插入大量记录,请将一个slice传递给Create方法 ,gorm将生成单独一条sql语句来插入所有的数据,并且返回填主键的值,钩子方法也会被调用

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)
for _, user := range users {
 user.ID // 1,2,3
}

使用CreateInBatches分批创建时,你可以指定每批的数量,例如:

var users = []User{{name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}
// 数量为 100
db.CreateInBatches(users, 100)

注意:

使用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创建记录时将调用这些钩子方法

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
 u.UUID = uuid.New()
   if u.Role == "admin" {
       return errors.New("invalid role")
  }
   return
}

根据map创建

gorm支持根据map【string】interface{}创建记录

db.Model(&User{}).Create(map[string]interface{}{
 "Name": "jinzhu", "Age": 18,
})
// batch insert from `[]map[string]interface{}{}`
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "jinzhu_1", "Age": 18},
{"Name": "jinzhu_2", "Age": 20},
})

默认值

您可以通过标签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:(-);"`
}

Upsert冲突

gorm为不同数据库提供了兼容的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", ...;

Gorm查询记录

检索单个对象

gorm提供了first,take,last方法 ,以便从数据库检索单个对象,当查询数据库时它添加了LIMIT 1 条件,且没有找到记录时,它会返回ErrRecordNotFound错误

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

如果你想避免ErrRecordNotFound错误,你可以使用FInd,比如db.Limt(1).Find(&user),find方法可以接受struct和slice的数据

First和Last会根据主键排序·,分别查询到第一条和最后一条记录,只有在目标struct是指针或者通过db.model()指定model时,该方法才有效,此外,如果model没有定义主键,那么将按照model的第一个字段进行排序,例如:

var user User
var users []User  
// 有效,因为目标 struct 是指针
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 有效,因为通过 `db.Model()` 指定了 model
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// 无效
result := map[string]interface{}{}
db.Table("users").First(&result)
// 配合 Take 有效
result := map[string]interface{}{}
db.Table("users").Take(&result)
// 未指定主键,会根据第一个字段排序(即:`Code`)
type Language struct {
 Code string
 Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

用主键检索

如果主键是数字类型,您可以使用内联条件连检索对象,传入字符串参数时,需要特别注意sql注入问题

db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

如果主键是字符串,查询将被写成这样:

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

检索全部对象

// 获取全部记录
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // 返回找到的记录数,相当于 `len(users)`
result.Error        // returns error

条件:

String条件

// 获取第一条匹配的记录
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// 获取全部匹配的记录
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和map条件

// 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;
// 主键切片条件
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

注意 当使用结构作为条件查询时,gorm只会查询非零字段值,这意味着如果您的字段值为0,false,‘’该字段就不会用于构建查询条件,例如:

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

如果想要包含零值查询条件,你可以使用map,其会包含所有key——value的查询条件,例如:

db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

指定结构体查询字段

当使用struct进行查询时,你可以通过where()传入struct来指定查询条件的字段,值,表名,例如:

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

内联条件

查询条件也可以被内联到First和Find方法之中,其用法类似于where。

// 根据主键获取记录,如果是非整型主键
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条件

构建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;
// 不在主键切片中的记录
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) 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);

选择特定字段

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;

Order

指定从数据库検索记録时的排序方式

db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// 多个 order
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

limit指定获取记录的最大数量

offset指定在开始返回记录之前要跳过的记录数量

db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// 通过 -1 消除 Limit 条件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// 通过 -1 消除 Offset 条件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)

Group By & Having

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

Distinct

从模型中选择不同的值

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

joins

指定joins条件

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)
// 带参数的多表连接
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预加载您可以使用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;
/*这段代码是使用 Golang 编程语言和 GORM 库进行数据库访问。
`db.Joins("Company")` 指定了对关联表 `Company` 进行连接操作。这个操作会将 `users` 表和 `Company` 表进行连接,以便查询关联数据。
`.Find(&users)` 是对查询对象进行的方法调用,其中 `&users` 是一个指向变量 `users` 的指针。这个方法会将查询结果存储在 `users` 变量中。
因此,这段代码的作用是从数据库中检索符合条件的记录,并将它们存储在 `users` 变量中。这里使用了 `Joins` 方法,指定了对关联表 `Company` 进行连接操作。这样,在查询 `users` 表时,同时也会查询关联表 `Company`,并将关联数据合并到查询结果中。
需要注意的是,为了使用 `Joins` 方法,需要在模型定义中定义 `Company` 的关联关系,例如使用 `BelongsTo` 或 `HasOne` 方法来定义 `users` 表与 `Company` 表的关联关系。否则,在使用 `Joins` 方法时会出现错误。*/

扫描结果

使用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)
这段代码是使用 Golang 编程语言和 GORM 库进行数据库访问。
/*
`db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio")` 是使用原生 SQL 语句进行查询。这个 SQL 语句查询了 `users` 表中符合条件 `name` 字段值为 "Antonio" 的记录,并选择了 `name` 和 `age` 两个字段进行返回。
`.Scan(&result)` 是对查询结果进行的方法调用,其中 `&result` 是一个指向变量 `result` 的指针。这个方法会将查询结果存储在 `result` 变量中。
因此,这段代码的作用是使用原生 SQL 语句查询数据库,选择 `users` 表中符合条件 `name` 字段值为 "Antonio" 的记录,并将查询结果的 `name` 和 `age` 两个字段存储在 `result` 变量中。需要注意的是,`result` 变量的类型必须与选择的字段类型匹配,否则会出现类型转换错误。*/

Gorm高级查询

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(),&gorm.Config{
QueryFields : true,
})
db.Find(&user)
//select users.name,users.age,... from users
//带上这个选项
//ssession mode 
db.Session(&gorm.Session{QueryFields : true}).Find(&user)
//select users,name users.age from users
Locking(FOR UPDATE)

gorm支持多种类型对的锁,例如:

db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
/*SELECT * FROM `users` FOR UPDATE。这段代码是使用 GORM 查询数据库的语句,具体含义如下:
- `db`:是一个 GORM 的数据库连接对象;
- `Clauses`:是一个 GORM 提供的方法,用于添加查询条件;
- `clause.Locking{Strength: "UPDATE"}`:是一个 GORM 的锁定查询条件,表示使用 `UPDATE` 锁对查询结果进行锁定。这意味着在查询期间,其他事务不能修改查询结果,直到当前事务结束;
- `Find`:是一个 GORM 提供的方法,用于根据条件查询数据库中的记录;
- `&users`:是一个指向 `users` 变量地址的指针,表示查询结果将被存储在该变量中。
因此,这段代码的作用是查询数据库中符合特定条件的记录,并使用 `UPDATE` 锁对查询结果进行锁定,然后将查询结果存储在 `users` 变量中。*/
db.Clauses(clause.Locking{
  Strength: "SHARE",
  Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
/*这段代码也是使用 GORM 查询数据库的语句,与前一个例子相似,只是使用了 `SHARE` 锁来锁定查询结果。具体含义如下:
- `db`:是一个 GORM 的数据库连接对象;
- `Clauses`:是一个 GORM 提供的方法,用于添加查询条件;
- `clause.Locking{Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}}`:是一个 GORM 的锁定查询条件,表示使用 `SHARE` 锁对查询结果进行锁定。`SHARE` 锁允许其他事务读取查询结果,但是不能修改查询结果。`Table` 参数指定了要锁定的表,`clause.CurrentTable` 是一个 GORM 提供的常量,表示当前查询的主表;
- `Find`:是一个 GORM 提供的方法,用于根据条件查询数据库中的记录;
- `&users`:是一个指向 `users` 变量地址的指针,表示查询结果将被存储在该变量中。
因此,这段代码的作用是查询数据库中符合特定条件的记录,并使用 `SHARE` 锁对查询结果进行锁定,然后将查询结果存储在 `users` 变量中。与前一个例子相比,这个例子使用的是更轻量级的锁,允许其他事务读取查询结果,但不允许修改查询结果。*/
// SELECT * FROM `users` FOR SHARE OF `users`
db.Clauses(clause.Locking{
  Strength: "UPDATE",
  Options: "NOWAIT",
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
/*在 GORM 中,`Options` 是锁定查询条件中的一个参数,用于指定锁的选项或附加属性。`Options` 参数可以与 `Strength` 参数一起使用,以进一步控制锁的行为和性能。
在上面的例子中,`Options` 参数指定了锁的选项,可以取以下值之一:
- `NOWAIT`:表示使用非阻塞锁,即如果无法立即获取锁,查询将立即失败,并返回错误。这可以避免长时间等待锁的情况,提高了查询的响应性和性能;
- `SKIP LOCKED`:表示跳过已被其他事务锁定的行,只查询未被锁定的行。这可以避免等待锁的情况,提高了查询的并发性和性能。
在 GORM 中,还可以使用其他的锁选项,例如 `FOR UPDATE WAIT n`、`FOR SHARE NOWAIT` 等,具体使用哪种锁选项取决于具体的场景和需求。
需要注意的是,不是所有的数据库都支持所有的锁级别和锁选项,因此在使用 GORM 锁定查询时,需要根据具体的数据库类型和版本来选择适当的锁级别和锁选项。*/

子查询

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

db.Where("amout > (?)",db.table("orders").Select("AVG(amout)")).Find(&orders)
select * from orders where amout > (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)  

From 子查询

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条件

使用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

带多个列的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.NameArg和map【string】{}{}形式的命名参数,例如:

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

Find至map

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

获取第一条匹配的记录,或者根据给定的条件初始化一个实例(仅支持struct和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}

如果没有找到记录,可以使用包含更多属性的结构体创建记录。Attes不会被用于生成查询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`
/*这段代码是使用 GORM 进行数据库查询的语句,具体含义如下:
- `db`:是一个 GORM 的数据库连接对象;
- `Clauses`:是一个 GORM 提供的方法,用于添加查询条件;
- `hints.New("MAX_EXECUTION_TIME(10000)")`:是一个 GORM 的查询提示(hint),表示将 "MAX_EXECUTION_TIME" 查询提示设置为 10000 毫秒。查询提示是一种用于向数据库发送特定指令的机制,可以影响数据库的执行计划和查询结果。在本例中,"MAX_EXECUTION_TIME" 查询提示表示设置查询的最长执行时间为 10000 毫秒;
- `Find`:是一个 GORM 提供的方法,用于根据条件查询数据库中的记录;
- `&User{}`:是一个指向 `User` 结构体变量地址的指针,表示查询结果将被存储在该变量中。
因此,这段代码的作用是查询数据库中的所有 `User` 记录,并将查询结果存储在 `User` 结构体变量中,并且使用 "MAX_EXECUTION_TIME" 查询提示来限制查询的最长执行时间为 10000 毫秒。这可以防止查询操作执行时间过长而导致系统出现性能问题或超时错误。
需要注意的是,查询提示是不同数据库之间的可移植性较差的特性,不同的数据库可能支持不同的查询提示语法和选项。因此,在使用查询提示时,需要根据具体的数据库类型和版本来选择适当的查询提示选项,以确保查询提示能够正确地生效。*/

索引提示允许传递索引提示到数据库,以防查询计划器出现混乱。

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 进行数据库查询的语句,具体含义如下:
- `db`:是一个 GORM 的数据库连接对象;
- `Clauses`:是一个 GORM 提供的方法,用于添加查询条件;
- `hints.ForceIndex("idx_user_name", "idx_user_id")`:是一个 GORM 的查询提示(hint),表示将查询提示设置为使用 "idx_user_name" 和 "idx_user_id" 两个索引来执行查询操作。查询提示是一种用于向数据库发送特定指令的机制,可以影响数据库的执行计划和查询结果。在本例中,"ForceIndex" 查询提示表示强制使用指定的索引来执行查询操作;
- `ForJoin()`:是一个 GORM 提供的方法,用于将查询提示应用于关联查询中的所有表。这可以确保查询提示对关联查询中的所有表都生效;
- `Find`:是一个 GORM 提供的方法,用于根据条件查询数据库中的记录;
- `&User{}`:是一个指向 `User` 结构体变量地址的指针,表示查询结果将被存储在该变量中。
因此,这段代码的作用是查询数据库中的所有 `User` 记录,并将查询结果存储在 `User` 结构体变量中,并且使用 "ForceIndex" 查询提示来强制使用 "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)
  // 业务逻辑...
}

FindInBatches

用于批量查询并且处理记录

// 每次批量处理 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 // 整个批量操作影响的记录数

查询钩子

func (u *User) AfterFind(tx *gorm.DB) (err error) {
  if u.Role == "" {
    u.Role = "user"
  }
  return
}

Pluck

Pluck用于从数据库查询单个列,并将结果扫描到切片,如果您想查询多列,您应该使用Select 和Sacn

Scope

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 且已付款或已发货的订单

Count

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

gorm更新

保存所有字段

Save会保存所有字段,几遍字段是零值

db.First(&user)
user.Name = "jinzhu 2"
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获取详情,当使用lmodel放大,一切该对象主键有值,改制会被用于构建条件,例如:

更新多个列

Updates 方法支持struct和map【string】interface{}参数,当使用struct更新时 ,默认情况下,gorm会只更新非零值的字段

// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

注意 当通过 struct更新时 ,gorm只会更新非零字段,如果您想确保指定字段被更新,你应该使用select更新选定的字段,或者使用,map来完成更新操作

更新选定的字段

如果您想要在更新时选定,忽略某些字段 ,您可以使用select ,omit

// 使用 Map 进行 Select
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// 使用 Struct 进行 Select(会 select 零值的字段)
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 所有字段(查询包括零值字段的所有字段)
db.Model(&user).Select("*").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
// Select 除 Role 外的所有字段(包括零值字段的所有字段)
db.Model(&user).Select("*").Omit("Role").Update(User{Name: "jinzhu", Role: "admin", Age: 0})

更新hook

批量更新–

如果您尚未通过Model指定记录的主键,则Gorm会执行批量更新

// 根据 struct 更新
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
// 根据 map 更新
db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
批量更新

如果您尚未通过model指定记录的主键,则gorm会执行批量更新

// 根据 struct 更新
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
// 根据 map 更新
db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

阻止全局更新

如果在任何条件下的情况下进行批量更新操作。默认情况下 ,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
db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu"

更新的记录条数

获取受更新影响的行数

// 通过 `RowsAffected` 得到更新的记录数
result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
result.RowsAffected // 更新的记录数
result.Error        // 更新的错误

高级选项

使用sql表达式更新

gorm’ 允许使用sql表达式更新列,例如:

// product 的 ID 是 `3`
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;
db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 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"))
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")})

不适用hook和事件追踪

如果您想早更新时跳过hook方法且不追踪更新时间,可以使用UpdateColumn,UpdateCloumns,其用法类似于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;

返回修改的数据

如果想要返回被修改的数据 ,进适用于支持Returning的数据库 ,例如:

// 返回所有列
var users []User
DB.Model(&users).Clauses(clause.Returning{}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
// UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING *
// users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}
// 返回指定的列
DB.Model(&users).Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
// UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING `name`, `salary`
// users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}

检查字段是否有变更?

gorm提供了changed方法 他可以被使用Before Update Hook里,他会返回字段是否有变更的布尔值

changed方法只能与Update updated 方法一起使用 并且 他只是检查Model对象字段的值与 update updated 的值 是否 相等 ,如果值有变更 且字段没有被忽略 ,就返回true

func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
  // 如果 Role 字段有变更
    if tx.Statement.Changed("Role") {
    return errors.New("role not allowed to change")
    }
  if tx.Statement.Changed("Name", "Admin") { // 如果 Name 或 Role 字段有变更
    tx.Statement.SetColumn("Age", 18)
  }
  // 如果任意字段有变更
    if tx.Statement.Changed() {
        tx.Statement.SetColumn("RefreshedAt", time.Now())
    }
    return nil
}
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu2"})
// Changed("Name") => true
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu"})
// Changed("Name") => false, 因为 `Name` 没有变更
db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{
  "name": "jinzhu2", "admin": false,
})
// Changed("Name") => false, 因为 `Name` 没有被 Select 选中并更新
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu2"})
// Changed("Name") => true
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu"})
// Changed("Name") => false, 因为 `Name` 没有变更
db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"})
// Changed("Name") => false, 因为 `Name` 没有被 Select 选中并更新

在 Update时修改值

若要在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") {
    s.Age += 20
    tx.Statement.SetColumn("Age", s.Age+20)
  }
}
db.Model(&user).Update("Name", "jinzhu")

gorm删除

删除一条记录

删除一条记录时 删除对象需要指定对象 ,否则就会触发批量Delete,例如:

// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;
// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

根据主键删除

gorm允许通过主键 (可以是复合主键)和内联条件来删除对象 ,他可以使用数字 也可以使用 字符串

db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;
db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

Delete hook

对于删除操作 gorm 支持 BeforeDelete ,AfterDelete ,Hook ,在删除记录时会调用这些方法

func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to delete")
    }
    return
}

批量删除

如果指定的删除 不包括主属性 name gorm会执行批量删除 ,他将删除 所有匹配的记录

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

阻止全局删除

如果在没有任何条件的情况下进行批量删除,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已经包含了该字段),他将自动获得软删除的能力!

拥有软删除能力的模型调用deldete时 记录不会被数据库。但是Gorm会将DeleteAt置为当前时间,并且你不能再通过普通的查询方法找到该记录。

// 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找到被软删除的记录

db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

永久删除

您也可以使用Unscoped永久删除匹配的记录

db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;

Delete Flag

将unix时间戳作为delete flag

import "gorm.io/plugin/soft_delete"
type User struct {
  ID        uint
  Name      string
  DeletedAt soft_delete.DeletedAt
}
// 查询
SELECT * FROM users WHERE deleted_at = 0;
// 删除
UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;
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"`
}
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构建器

原生构造器

原生查询sql和Scan

type Result struct {
  ID   int
  Name string
  Age  int
}
var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
var age int
db.Raw("SELECT SUM(age) FROM users WHERE role = ?", "admin").Scan(&age)
var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&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.NameArg,map【string】interface{}或者是struct形式的命名参数,例如:

db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
// SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"
db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu2"}).First(&result3)
// SELECT * FROM `users` WHERE name1 = "jinzhu2" OR name2 = "jinzhu2" ORDER BY `users`.`id` LIMIT 1
// 原生 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"

DryRun模式

stmt := db.Session(&Session{DryRun: true}).First(&user, 1).Statement
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
stmt.Vars         //=> []interface{}{1}

在不执行的情况下生成sql及其参数,可以用于准备或测试生成的sql

首先,代码定义了一个变量 stmt,它通过调用 db.Session(&Session{DryRun: true}).First(&user, 1).Statement 得到。

这个语句的意思是,从数据库中查询 ID 为 1 的用户记录,并将结果存储在 user 变量中。在查询过程中,使用了 DryRun 参数来告诉 GORM 不要实际执行 SQL 查询(即只生成 SQL 语句但不执行),而是先生成一份预览 SQL 语句的对象并返回。

接着,我们通过 stmt.SQL.String() 和 stmt.Vars 分别获取了这个对象所代表的 SQL 语句和参数值。stmt.SQL.String() 返回的结果是一个字符串,内容就是 “SELECT * FROM usersWHEREid= $1 ORDER BYid”,表示要查询名为 users 的数据表中 ID 为 1 的记录。其中 $1 是参数占位符,表示在实际执行语句时将被替换成第一个参数的值。stmt.Vars 则返回一个空列表,因为在 DryRun 模式下不存在实际的参数绑定。

ToSQL

返回生成的sql但是不执行

gorm使用 database、sql的参数占位符来构建sql语句 它会自动 转义参数以避免sql注入问题但我们不保证生成sql的安全 只是用于调试。

gosql := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
  return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
})
sql //=> SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10

Row & Rows

获取 *sql.Row结果

// 使用 GORM API 构建 SQL
row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)
// 使用原生 SQL
row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
row.Scan(&name, &age, &email)

获得* sql.Rows 结果

// 使用 GORM API 构建 SQL
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows()
defer rows.Close()
for rows.Next() {
  rows.Scan(&name, &age, &email)
  // 业务逻辑...
}
// 原生 SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows()
defer rows.Close()
for rows.Next() {
  rows.Scan(&name, &age, &email)
  // 业务逻辑...
}

转到FindInBatches 获取如何在批量查询和初六记录的信息,转到Group条件 获取如何构建复杂sql查询的信息

将sql.Rows扫描到model

使用ScanRows 江一行记录扫描到struct中,例如:

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
var user User
for rows.Next() {
  // ScanRows 将一行扫描至 user
  db.ScanRows(rows, &user)
  // 业务逻辑...
}

Connection

在同一个 db tcp连接中运行多个sql (不在事务中的)

db.Connection(func(tx *gorm.DB) error {
  tx.Exec("SET my.role = ?", "admin")
  tx.First(&User{})
})

高级应用

字句(Clause)

gorm使用sql builder在内部生成sql,对于每个操作 ,gorm创建一个Gorm.Statement对象,所有gorm api添加、 更改 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

子句构造器

对于不同的数据库 子句可能会产生不同的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定义了许多子句,并且一些子句提供了可用于您的应用程序的高级选项尽管他们中的大多数很少使用 ,但是如果您发现gorm公共API无法满足您的要求 不妨检查一下 ,例如:

db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&user)
// INSERT IGNORE INTO users (name,age...) VALUES ("jinzhu",18...);

语句修饰符

gorm 提供解耦StatementModifier允许您修改语句一匹配您的要求 以limit为例:

import "gorm.io/hints"
db.Clauses(hints.New("hint")).Find(&User{})
/*这段代码是使用 GORM 库编写的 Go 代码。以下是代码的解释:

db.Clauses(hints.New(“hint”)).Find(&User{})

- `db`:表示使用 GORM 库建立的数据库连接。
- `Clauses`:是一个方法,用于向 GORM 生成的 SQL 查询语句中添加额外的语句。
- `hints.New("hint")`:是一种提示语句,它指示数据库优化器在执行查询时使用特定的提示信息。提示语句是一种向数据库提供有关如何执行查询的附加信息的指令。在这种情况下,提示语句是“hint”。
- `Find(&User{})`:是一种方法,用于执行查询并检索结果。它告诉 GORM 查找所有 `User` 模型的记录并返回它们。
因此,这段代码的含义是执行查询以查找 `User` 模型的所有记录,并在执行查询时向数据库优化器提供提示信息。具体使用的提示信息是“hint”,但是没有更多的上下文信息,因此无法确定这个提示语句的具体作用或为什么需要使用它。*/
// SELECT * /*+ hint */ FROM `users`

GORM Belongs To

Belongs To

belongs to 会与另一个模型建立一对一的连接,这种模型的没一个实例都属于另一个模型的一个实例

例如:

您的应用包含User和company 并且每一个user都能且只能被分配给一个company 。下面的类型就表示这种 关系 注意在user对象中 ,有一个和Company一样的compangId。默认的情况下,companyId被隐含的表示在userhe1Company之间创建一个外键关系,因此必须包含在结构体中才能填充字段Company内部结构体

// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {
  gorm.Model
  Name      string
  CompanyID int
  Company   Company
}
type Company struct {
  ID   int
  Name string
}

重写外键

要定义一个belongs to关系 数据库的表中必须存在外键。默认情况下 外间的名字i使用拥有者的类型名称加上表的主键的字段名字

例如:

定义一个User实体属于Company石头 name外间的名字一般使用CompanyId

GORm同时提供 自定义外键名字的方式 如下例所示:

type User struct {
  gorm.Model
  Name         string
  CompanyRefer int
  Company      Company `gorm:"foreignKey:CompanyRefer"`
  // 使用 CompanyRefer 作为外键
}
type Company struct {
  ID   int
  Name string
}

重写引用

对于belongs to 关系 ,gorm通常使用数据库表 ,主表(拥有者)的主键值作为外键参考 ,正如上边的例子,我们使用主表company中的主键字段id作为外值的参考值。

如果在Company实体中设置了user实体 namegorm会自动把Company中的ID属性值保存到User的CompanyId属性值中。

同样的 ,您也可以使用标签references来改变它,例如:

type User struct {
  gorm.Model
  Name      string
  CompanyID string
  Company   Company `gorm:"references:Code"` // 使用 Code 作为引用
}
type Company struct {
  ID   int
  Code string
  Name string
}

Belongs to 的curd

点击关联模式连接获取belongs to的相关的用法

在 Golang 中,没有像 ActiveRecord 那样的 ORM 框架来处理关联关系,但是可以通过在模型中定义结构体来模拟 Belongs to 关联关系。下面是 Golang 中 Belongs to 的 CURD 操作的基本步骤:

  1. 创建数据:在创建数据时,需要先创建主表的记录,然后再创建从表的记录,并将从表的外键设置为主表记录的 ID。例如,如果我们有一个 User 结构体和一个 Comment 结构体,其中每个评论都属于一个用户,那么在创建评论时需要将评论的 UserID 字段设置为对应用户的 ID。

    user := User{Name: "Alice"}
    db.Create(&user)
    comment := Comment{Body: "Hello World!", UserID: user.ID}
    db.Create(&comment)
    ```
    
  2. 读取数据:在读取数据时,可以使用 GORM 的 Preload 方法预加载关联的用户信息。例如,如果我们要查询所有评论以及对应的用户信息,可以使用以下代码:

    var comments []Comment
    db.Preload("User").Find(&comments)
    for _, comment := range comments {
        fmt.Printf("%s: %s\n", comment.User.Name, comment.Body)
    }
    ```
    
  3. 更新数据:在更新数据时,需要先更新主表的记录,然后再更新从表的记录。例如,如果我们要更新一条评论的用户信息,可以使用以下代码:

    comment := Comment{ID: 1}
    db.Model(&comment).Update("UserID", 2)
    ```
    
  4. 删除数据:在删除数据时,如果从表的记录有外键约束,需要先删除从表的记录,然后再删除主表的记录。例如,如果我们要删除一个用户及其所有的评论,可以使用以下代码:

    user := User{ID: 1}
    db.Delete(&user)
    comments := []Comment{}
    db.Where("user_id = ?", user.ID).Delete(&comments)
    ```
    

这些是 Golang 中 Belongs to 关联关系的 CURD 操作的基本步骤。需要注意的是,在进行操作时需要注意主表和从表之间的关系,以避免数据不一致的情况发生。

预加载

gorm 允许啊通过使用Preload或者Joins来主动加载实体的关联关系i 具体内容请参考,预加载(主动加载)

外键约束

你可以通过 OnUpdate ,OnDelete 配置标签来增加关联关系的级联操作,如下边的粒子 通过gorm可以完成用户和公司的级联更新和级联删除操作

type User struct {
  gorm.Model
  Name      string
  CompanyID int
  Company   Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type Company struct {
  ID   int
  Name string
}

Gorm Has One 关系

Has One

has one 与另一个模型建立一对一关系i的关联,但是他和一对一关系有些不同 ,这种关联表明一个模型的每个实例都包含或者拥有一个模型的实例

例如:您的应用中包含Usre和Credit card 模型 且每个user只能有一张credit card。

// User 有一张 CreditCard,UserID 是外键
type User struct {
  gorm.Model
  CreditCard CreditCard
}
type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}

重写外键

对于has one关系 ,同样必须存在外键 字段。拥有者将会吧属于他的模型的主键保存到这个字段

这个字段的名称通常是由has one 模型的类型加上其主键生成 对于上边的里子 他是 UserId。

为User添加Credit card 时 它会将User的Id保存到自己的UseriId字段

如果你想使用另一个字段来保存这个关系 ,你同样可以使用ForeignKey来改变它 例如:

type User struct {
  gorm.Model
  CreditCard CreditCard `gorm:"foreignKey:UserName"`
  // 使用 UserName 作为外键
}
type CreditCard struct {
  gorm.Model
  Number   string
  UserName string
}

重写引用

在默认的情况下 ,拥有者实体都会将has one 对应模型的主键保存为外键 ,你可以修改它,用另一个字段保存,例如下边的这个使用name来保存的例子您可以使用标签 reference来更改他 ,例如:

type User struct {
  gorm.Model
  Name       string     `gorm:"index"`
  CreditCard CreditCard `gorm:"foreignkey:UserName;references:name"`
}
type CreditCard struct {
  gorm.Model
  Number   string
  UserName string
}

多态关联·

GORM为has one 和has many 提供了多态关联支持,它会将拥有者实体的表名 主键值 都保存到多态类型的字段中

type Cat struct {
  ID    int
  Name  string
  Toy   Toy `gorm:"polymorphic:Owner;"`
}
type Dog struct {
  ID   int
  Name string
  Toy  Toy `gorm:"polymorphic:Owner;"`
}
type Toy struct {
  ID        int
  Name      string
  OwnerID   int
  OwnerType string
}
db.Create(&Dog{Name: "dog1", Toy: Toy{Name: "toy1"}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","dogs")

您可以使用标签polymorphicValue来更改多态类型的值,例如:

type Dog struct {
  ID   int
  Name string
  Toy  Toy `gorm:"polymorphic:Owner;polymorphicValue:master"`
/*这是 GORM 中的一个标签(tag),用于在定义模型结构体时指定多态关系模型。具体来说,这个标签中的 `polymorphic:Owner` 和 `polymorphicValue:master` 分别表示在 `Owner` 字段上建立多态关联,并将 `OwnerType` 字段的值设置为 `master`。
具体解释如下:
- `polymorphic:Owner` 表示在 `Owner` 字段上建立多态关联。这个值可以是任何字符串,只要它是从表中存在的字段名称即可。如果不指定这个标签,GORM 将默认使用 `polymorphic` 作为多态关联的外键名称。
- `polymorphicValue:master` 表示将多态关联的类型设置为 `master`。这个值可以是任何字符串,只要它能唯一标识多态关联的类型即可。
我们可以通过在 GORM 的模型结构体中定义这个标签来指定多态关系模型。例如,以下是一个 `Comment` 模型和一个 `Image` 模型,其中每个评论都可以关联到不同的资源,比如文章、图片或视频。我们使用 `Owner` 字段作为多态关联的外键,并将 `OwnerType` 字段的值设置为 `master`:
````go
type Comment struct {
    gorm.Model
    Body       string
    Owner      *Image `gorm:"polymorphic:Owner;polymorphicValue:master"`
    OwnerID    uint
    OwnerType  string
}
type Image struct {
    gorm.Model
    URL        string
    Comments   []*Comment `gorm:"polymorphic:Owner;polymorphicValue:master"`
}*/

这里的 Owner 字段指定了一个多态 Belongs to 的关联关系,同时使用了 polymorphic 和 polymorphicValue 标签来指定多态关联的外键和类型。由于设置了这些标签,GORM 将使用 Owner 字段作为多态关联的外键,并将 OwnerType 字段的值设置为 master。这将建立起主表和从表之间的多态关联关系,以便我们可以轻松地查询每个评论所关联的资源。

需要注意的是,如果我们在模型中使用了 polymorphic 标签来指定多态关联的外键名称,则必须在主表和从表中都定义这个字段,并为其创建索引,以便查询时能够快速访问它。

}

type Toy struct {

ID int

Name string

OwnerID int

OwnerType string

}

db.Create(&Dog{Name: “dog1”, Toy: Toy{Name: “toy1”}})

// INSERT INTO dogs (name) VALUES (“dog1”)

// INSERT INTO toys (name,owner_id,owner_type) VALUES (“toy1”,“1”,“master”)

自引用 Has one

type User struct {
  gorm.Model
  Name      string
  ManagerID *uint
  Manager   *User
}

外键约束

你可以通过为标签constraint 配置 OnUpdate。OnDelete实现外键约束,在使用gorm进行迁移时它会被创建,例如:

type User struct {
  gorm.Model
  CreditCard CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}

你也可以在删除记录时通过Select来删除关联的记录 查看 Delete with Select 获取详情。

Gorm HasMany 关系

Has Many

has many与另一个模型建立了一对多的连接 不同于一对一 has one ,拥有者可以有零个或者多个关联模型

例如 您的应用中包含User和 Credit car 模型 且每个user可以有多个credit card

// User 有多张 CreditCard,UserID 是外键
type User struct {
  gorm.Model
  CreditCards []CreditCard
}
type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}

重写外键

要定义 has many 关系 ,同样必须有存在外键。默认的键名是拥有者的类型名加上其主键字段的名 例如:

要定义一个属于User的模型 ,其外键应该是UserId

此外,想要使用另一个字段作为你外键,您可以使用ForeignKey标签自定义他:

type User struct {
  gorm.Model
  CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}
type CreditCard struct {
  gorm.Model
  Number    string
  UserRefer uint
}

重写引用

gorm通常使用拥有者的主键作为外键的值,对于上边的里子 他只是user的id字段。

为user添加credit card 时 ,gorm会将user的ID字段保存到Credit card的userId字段

同样的,您也可以使用references来更改它,例如:

type User struct {
  gorm.Model
  MemberNumber string
  CreditCards  []CreditCard `gorm:"foreignKey:UserNumber;references:MemberNumber"`
}
type CreditCard struct {
  gorm.Model
  Number     string
  UserNumber string
}

多态关联等同has one


Gorm many to many关系

Many To Many

many to many 会在俩个model中添加一张中间连接表例如:

您的应用包含了user和language,切一个user可以说多种language,多个User也可以说一种language

// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}
type Language struct {
  gorm.Model
  Name string
}

当使用gorm的 AutoMigrate、为User创建表时,GORM会自动创建链接表

反向引用

// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
  gorm.Model
  Languages []*Language `gorm:"many2many:user_languages;"`
}
type Language struct {
  gorm.Model
  Name string
  Users []*User `gorm:"many2many:user_languages;"`
}

实体关联

自动创建,更新

在创建,更新纪录时,gorm会通过Upsert自动保存关联及其引用记录。

user := User{
  Name:            "jinzhu",
  BillingAddress:  Address{Address1: "Billing Address - Address 1"},
  ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
  Emails:          []Email{
    {Email: "jinzhu@example.com"},
    {Email: "jinzhu-2@example.com"},
  },
  Languages:       []Language{
    {Name: "ZH"},
    {Name: "EN"},
  },
}
db.Create(&user)
// BEGIN TRANSACTION;
// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "languages" ("name") VALUES ('ZH'), ('EN') ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "user_languages" ("user_id","language_id") VALUES (111, 1), (111, 2) ON DUPLICATE KEY DO NOTHING;
// COMMIT;
db.Save(&user)

如果您想要更新关联的数据 ,您应该改使用FullSaveAssociations模式:

db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
// ...
// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1);
// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email);
// ...

跳过自动创建,更新

若要在创建,更新时跳过自动保存 ,您可以使用Select 或者Omit,例如:

user := User{
  Name:            "jinzhu",
  BillingAddress:  Address{Address1: "Billing Address - Address 1"},
  ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
  Emails:          []Email{
    {Email: "jinzhu@example.com"},
    {Email: "jinzhu-2@example.com"},
  },
  Languages:       []Language{
    {Name: "ZH"},
    {Name: "EN"},
  },
}
db.Select("Name").Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);
db.Omit("BillingAddress").Create(&user)
// Skip create BillingAddress when creating a user
db.Omit(clause.Associations).Create(&user)
// Skip all associations when creating a user

Note:对于 many2many关联,gorm再创建链接表引用之前,会先upsert关联。如果您想跳过关联的upsert,你可以这样做:

db.Omit("Languages.*").Create(&user)

下面的代码将跳过创建关联及其引用

db.Omit("Languages").Create(&user)
/*
这两个语句都使用了GORM库中的Omit方法,用于在创建/更新记录时排除指定的字段。具体而言,这两个语句的区别在于排除的字段不同。
`db.Omit("Languages.*").Create(&user)`语句将排除"Languages"字段中的所有属性,这意味着在创建用户记录时,将不会存储用户的语言设置。此外,如果"Languages"字段包含一个嵌套的结构体或切片,也将排除其所有属性。例如,如果"Languages"字段的类型是"[]Language",则该语句将排除"Language"结构体中的所有属性。
`db.Omit("Languages").Create(&user)`语句将排除"Languages"字段本身,但不会排除嵌套在该字段中的属性。这意味着在创建用户记录时,将存储用户的语言设置,但不会存储"Languages"字段本身。例如,如果"Languages"字段的类型是"[]Language",则该语句将存储"Language"结构体中的所有属性,但不会存储"Languages"切片本身。
下面是一个示例,说明这两个语句的区别:

type User struct {

Name string

Addresses []Address

Languages []Language

}

type Address struct {

Address1 string

City string

}

type Language struct {

Name string

}

user := User{

Name: “jinzhu”,

Addresses: []Address{

{Address1: “Billing Address - Address 1”, City: “Beijing”},

{Address1: “Shipping Address - Address 1”, City: “Shanghai”},

},

Languages: []Language{

{Name: “ZH”},

{Name: “EN”},

},

}

// 排除"Languages"字段

db.Omit(“Languages”).Create(&user)

// 排除"Languages."字段

db.Omit("Languages.").Create(&user)

在上述示例中,第一条语句将存储用户的姓名、地址和语言设置,但不会存储"Languages"字段本身。而第二条语句将存储用户的姓名和地址,但不会存储用户的语言设置。*/

Select/Omit关联字段

user := User{
  Name:            "jinzhu",
  BillingAddress:  Address{Address1: "Billing Address - Address 1", Address2: "addr2"},
  ShippingAddress: Address{Address1: "Shipping Address - Address 1", Address2: "addr2"},
}
// 创建 user 及其 BillingAddress、ShippingAddress
// 在创建 BillingAddress 时,仅使用其 address1、address2 字段,忽略其它字段
db.Select("BillingAddress.Address1", "BillingAddress.Address2").Create(&user)
db.Omit("BillingAddress.Address2", "BillingAddress.CreatedAt").Create(&user)

关联模式

关联模式包含一些在处理关系时有用的方法

//开启关联模式
var user User
db.Model(&user).Association("Languages")
//user 是源模型,他的主键不能为空
//关系的字段名是Language
//如果匹配了上面的俩个要求,会开始关联模式,否则会返回错误

查找关联

查找所有匹配的关联记录

db.Model(&user).Association(“Languages”).Find(&languages)

查找带条件的关联

codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages)
db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages)

添加关联

为many2many,has many添加新的关联,为has one ,belongs to 替换当前的关联

db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})
db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})

替换关联

用一个新的关联替换当前的关联

db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)

删除关联

如果存在,则删除源模型与参数之间的关系,只会删除引用,不会从数据库中删除这些对象。

db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)

在 GORM 中,删除引用指的是从一个模型对象的关联字段中删除与另一个模型对象的关联关系,而不删除这个被删除的模型对象本身。具体而言,这种操作使用 GORM 中的关联(Association)功能,通过指定模型对象和关联字段,将关联字段中与指定模型对象相关联的关联关系删除。

例如,假设我们有一个名为"user"的模型,它有一个名为"Languages"的关联字段,用于引用"Language"模型的语言设置。如果我们要从"user"模型的"Languages"字段中删除一个名为"English"的语言设置,但不删除"English"语言设置本身,我们可以使用以下代码:

var english Language
db.Where("name = ?", "English").First(&english)
db.Model(&user).Association("Languages").Delete(&english)

这段代码首先查询数据库中名称为"English"的"Language"模型对象,并将查询结果赋值给"english"变量。然后,使用"user"模型的"Association"方法和"Languages"关联字段,将"english"对象从"user"模型的"Languages"字段中删除。

需要注意的是,这种删除方式只会删除关联关系,而不会删除被删除对象本身。也就是说,在上面的例子中,"English"语言设置对象本身并没有被删除,只是从"user"模型的"Languages"字段中删除了对它的引用。

总之,删除引用指的是从一个模型对象的关联字段中删除与另一个模型对象的关联关系,而不删除被删除的模型对象本身。在 GORM 中,可以使用关联(Association)功能来实现这种操作。

关联计数

返回当前关联的计数

db.Model(&user).Association("Languages").Count()
// 条件计数
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).Association("Languages").Count()

批量处理数据

关联模式也支持批量处理,例如:

// 查询所有用户的所有角色
db.Model(&users).Association("Role").Find(&roles)
// 从所有 team 中删除 user A
db.Model(&users).Association("Team").Delete(&userA)
// 获取去重的用户所属 team 数量
db.Model(&users).Association("Team").Count()
// 对于批量数据的 `Append`、`Replace`,参数的长度必须与数据的长度相同,否则会返回 error
var users = []User{user1, user2, user3}
// 例如:现在有三个 user,Append userA 到 user1 的 team,Append userB 到 user2 的 team,Append userA、userB 和 userC 到 user3 的 team
db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC})
// 重置 user1 team 为 userA,重置 user2 的 team 为 userB,重置 user3 的 team 为 userA、 userB 和 userC
db.Model(&users).Association("Team").Replace(&userA, &userB, &[]User{userA, userB, userC})

带Select 的删除

你可以在删除记录时通过Select来删除具有has one,has many,many2many关系的记录,例如:

// 删除 user 时,也删除 user 的 account
db.Select("Account").Delete(&user)
// 删除 user 时,也删除 user 的 Orders、CreditCards 记录
db.Select("Orders", "CreditCards").Delete(&user)
// 删除 user 时,也删除用户所有 has one/many、many2many 记录
db.Select(clause.Associations).Delete(&user)
// 删除 users 时,也删除每一个 user 的 account
db.Select("Account").Delete(&users)

注意:

只有当记录的主键不为空时,关联才会被删除掉,gorm会使用这些主键作为条件来删除关联记录

db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{})
db.Select("Account").Where("name = ?", "jinzhu").Delete(&User{ID: 1})
db.Select("Account").Delete(&User{ID: 1})

关联标签

golangORM框架gorm详解(超详细),在这里插入图片描述,第2张

gorm会话

gorm提供了session方法,这是一个New Session Method ,他允许创建带配置的新建会话模式:

// Session 配置
type Session struct {
  DryRun                 bool
  PrepareStmt            bool
  NewDB                  bool
  SkipHooks              bool
  SkipDefaultTransaction bool
  AllowGlobalUpdate      bool
  FullSaveAssociations   bool
  Context                context.Context
  Logger                 logger.Interface
  NowFunc                func() time.Time
}