关联映射
GORM支持在模型结构体中定义关联映射,这里我们简单介绍一下关联映射的使用方法。
预加载和懒加载
使用关联查询时,我们可以使用预加载和懒加载两种方式。预加载会将关联模型对象一次性查询出来,懒加载则需要我们根据主键手动进行二次查询得到关联模型对象。下面例子中Category和CategoryDetail是具有一对一关系的关联模型,我们以此为例进行介绍。
预加载
main.go
package main
import (
"errors"
"fmt"
"github.com/gacfox/demoorm/conf"
"github.com/gacfox/demoorm/model"
"gorm.io/gorm"
)
func main() {
conf.InitDb()
var categoryDetail model.CategoryDetail
result := conf.Db.Preload("Category").Find(&categoryDetail, 1)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// 未查到数据
fmt.Println("未查询到数据")
} else if result.Error != nil {
// 其他错误
panic(result.Error)
}
fmt.Println(categoryDetail.Category)
}
上面代码使用了预加载方式,指定将CategoryDetail和Category同时查询出来,如果代码中不使用Preload()函数指定预加载的模型,GORM默认会进行懒加载(访问CategoryDetail.Category会得到一个主键为0的空结构体)。另外要注意这里Preload()的参数是要预加载的字段名(而不是模型结构体名),例如一对多中如果我们的关联字段叫Products,那么这里就要写为Preload("Products")。
此外Preload()也可以链式写法指定多个字段,或是在字段中使用点号.指定多层嵌套预加载,下面是一个例子。
result = conf.Db.Preload("Category").Preload("Category.Products").Preload("Category.Products.Tags").Find(&categoryDetail, 1)
懒加载
package main
import (
"errors"
"fmt"
"github.com/gacfox/demoorm/conf"
"github.com/gacfox/demoorm/model"
"gorm.io/gorm"
)
func main() {
conf.InitDb()
var categoryDetail model.CategoryDetail
result := conf.Db.Find(&categoryDetail, 1)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// 未查到数据
fmt.Println("未查询到数据")
} else if result.Error != nil {
// 其他错误
panic(result.Error)
}
var category model.Category
result = conf.Db.Find(&category, categoryDetail.CategoryId)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// 未查到数据
fmt.Println("未查询到数据")
} else if result.Error != nil {
// 其他错误
panic(result.Error)
}
fmt.Println(category)
}
上面代码使用了懒加载,我们第一次查询后只能获取CategoryId,我们根据这个外键继续查询得到Category。
一对一关联
GORM中一对一关联分为Belongs To和Has One两种模式,其实它们唯一的区别是关联的方向不同。
Belongs To
下面我们定义两个结构体模型Category和CategoryDetail,其中CategoryDetail包含了Category的引用,外键字段t_category_id也位于t_category_detail表中。
model/category.go
package model
type Category struct {
CategoryId int64 `gorm:"column:category_id;primaryKey;autoIncrement"`
CategoryName string `gorm:"column:category_name"`
}
func (*Category) TableName() string {
return "t_category"
}
model/category_detail.go
package model
type CategoryDetail struct {
CategoryDetailId int64 `gorm:"column:category_detail_id;primaryKey;autoIncrement"`
CategoryId int64 `gorm:"column:category_id"`
Category Category `gorm:"foreignKey:category_id"`
}
func (*CategoryDetail) TableName() string {
return "t_category_detail"
}
代码中我们主要关注CategoryDetail.Category字段的Tag,我们这里将其写为gorm:"foreignKey:category_id",即指定使用CategoryId字段作为关联外键。
Has One
Has One也是一对一关联模式,只不过模型上配置的关联方向和Belongs To相关。这里Category包含了CategoryDetail的引用,外键字段t_category_id位于t_category_detail表中和之前相同。
model/category.go
package model
type Category struct {
CategoryId int64 `gorm:"column:category_id;primaryKey;autoIncrement"`
CategoryName string `gorm:"column:category_name"`
CategoryDetail CategoryDetail `gorm:"foreignKey:category_id"`
}
func (*Category) TableName() string {
return "t_category"
}
model/category_detail.go
package model
type CategoryDetail struct {
CategoryDetailId int64 `gorm:"column:category_detail_id;primaryKey;autoIncrement"`
CategoryId int64 `gorm:"column:category_id"`
}
func (*CategoryDetail) TableName() string {
return "t_category_detail"
}
代码中结构体Tag上的内容也和之前相同,只不过结构体引用的方向与之前相反。
一对多关联
Has Many
Has Many即一对多关系,描述了一个模型关联多个模型数组的关联关系,下面是一个例子。
model/category.go
package model
type Category struct {
CategoryId int64 `gorm:"column:category_id;primaryKey;autoIncrement"`
CategoryName string `gorm:"column:category_name"`
Products []Product `gorm:"foreignKey:category_id"`
}
func (*Category) TableName() string {
return "t_category"
}
model/product.go
package model
import "time"
type Product struct {
ProductId int64 `gorm:"column:product_id;primaryKey;autoIncrement"`
Sku string `gorm:"column:sku"`
CategoryId int64 `gorm:"column:category_id"`
ProductName string `gorm:"column:product_name"`
Price string `gorm:"column:price"`
Store int32 `gorm:"column:store"`
CreateTime time.Time `gorm:"column:create_time"`
}
func (*Product) TableName() string {
return "t_product"
}
代码中,Category模型关联了多个Product模型,我们这里定义了Products字段,它是一个数组类型,在字段Tag中我们指明了关联模型中的外键。
此外,如果要描述一对多的反向关系,我们可以使用前面提到的Belongs To模式,这里就不多介绍了。
多对多关联
Many To Many
Many To Many描述了一种多对多关系,数据库中多对多关系需要一张额外的中间表来表达,这些信息都需要在关联模型的字段Tag中指定。下面例子中,Product和Tag模型具有多对多关联关系,它们使用一张中间表t_product_tag来记录多对多关联。
model/product.go
package model
import "time"
type Product struct {
ProductId int64 `gorm:"column:product_id;primaryKey;autoIncrement"`
Sku string `gorm:"column:sku"`
CategoryId int64 `gorm:"column:category_id"`
ProductName string `gorm:"column:product_name"`
Price string `gorm:"column:price"`
Store int32 `gorm:"column:store"`
CreateTime time.Time `gorm:"column:create_time"`
Tags []Tag `gorm:"many2many:t_product_tag;foreignKey:product_id;joinForeignKey:product_id;references:tag_id;joinReferences:tag_id"`
}
func (*Product) TableName() string {
return "t_product"
}
model/tag.go
package model
type Tag struct {
TagId int64 `gorm:"column:tag_id;primaryKey;autoIncrement"`
TagName string `gorm:"column:tag_name"`
}
func (*Tag) TableName() string {
return "t_tag"
}
我们主要关注Product模型中的Tags字段的Tag,其中指定了gorm:"many2many:t_product_tag;foreignKey:ProductId;joinForeignKey:product_id;references:TagId;joinReferences:tag_id":
many2many:指定多对多关联表表名foreignKey:指定当前模型的列名作为连接中间表的外键joinForeignKey:指定连接表的列名,用于和当前模型连接references:指定关联表的列名作为连接中间表的外键joinReferences指定连接表的列名,用于和关联模型连接