关联映射

GORM支持在模型结构体中定义关联映射,这里我们简单介绍一下关联映射的使用方法。

预加载和懒加载

使用关联查询时,我们可以使用预加载懒加载两种方式。预加载会将关联模型对象一次性查询出来,懒加载则需要我们根据主键手动进行二次查询得到关联模型对象。下面例子中CategoryCategoryDetail是具有一对一关系的关联模型,我们以此为例进行介绍。

预加载

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

上面代码使用了预加载方式,指定将CategoryDetailCategory同时查询出来,如果代码中不使用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 ToHas One两种模式,其实它们唯一的区别是关联的方向不同。

Belongs To

下面我们定义两个结构体模型CategoryCategoryDetail,其中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中指定。下面例子中,ProductTag模型具有多对多关联关系,它们使用一张中间表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指定连接表的列名,用于和关联模型连接
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。