对象关系映射模式(object relational Mapping)是为了解决面向对象和关系型数据库存在的互不匹配的问题,简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。
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) }
模型是标准的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风格
gorm允许通过标签为关联配置外键,约束,many2mang表
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{})
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提供了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条件 ,用法和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;
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;
指定从数据库検索记録时的排序方式
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指定在开始返回记录之前要跳过的记录数量
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)
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)
从模型中选择不同的值
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允许通过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
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)
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.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
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)
获取第一条匹配的记录,或者根据给定的条件初始化一个实例(仅支持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) // 业务逻辑... }
用于批量查询并且处理记录
// 每次批量处理 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用于从数据库查询单个列,并将结果扫描到切片,如果您想查询多列,您应该使用Select 和Sacn
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用于获取匹配的记录数
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
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})
如果您尚未通过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 // 更新的错误
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方法且不追踪更新时间,可以使用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 选中并更新
若要在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")
删除一条记录时 删除对象需要指定对象 ,否则就会触发批量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);
对于删除操作 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;
将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和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"
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 模式下不存在实际的参数绑定。
返回生成的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查询的信息
使用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) // 业务逻辑... }
在同一个 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`
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的相关的用法
在 Golang 中,没有像 ActiveRecord 那样的 ORM 框架来处理关联关系,但是可以通过在模型中定义结构体来模拟 Belongs to 关联关系。下面是 Golang 中 Belongs to 的 CURD 操作的基本步骤:
创建数据:在创建数据时,需要先创建主表的记录,然后再创建从表的记录,并将从表的外键设置为主表记录的 ID。例如,如果我们有一个 User 结构体和一个 Comment 结构体,其中每个评论都属于一个用户,那么在创建评论时需要将评论的 UserID 字段设置为对应用户的 ID。
user := User{Name: "Alice"} db.Create(&user) comment := Comment{Body: "Hello World!", UserID: user.ID} db.Create(&comment) ```
读取数据:在读取数据时,可以使用 GORM 的 Preload 方法预加载关联的用户信息。例如,如果我们要查询所有评论以及对应的用户信息,可以使用以下代码:
var comments []Comment db.Preload("User").Find(&comments) for _, comment := range comments { fmt.Printf("%s: %s\n", comment.User.Name, comment.Body) } ```
更新数据:在更新数据时,需要先更新主表的记录,然后再更新从表的记录。例如,如果我们要更新一条评论的用户信息,可以使用以下代码:
comment := Comment{ID: 1} db.Model(&comment).Update("UserID", 2) ```
删除数据:在删除数据时,如果从表的记录有外键约束,需要先删除从表的记录,然后再删除主表的记录。例如,如果我们要删除一个用户及其所有的评论,可以使用以下代码:
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 }
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”)
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 获取详情。
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 }
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来删除具有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})
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 }
上一篇:javaWeb项目:简易图书系统