Gorm 关联模型

社区文档的gorm关联模型,缺少实操数据的情况下,会让新同学不明觉厉,本文用实例说话,对新手更友好。

Struct vs Model

结构体本身只是描述数据的内存布局,可以视作经典类语言中的属性或状态部分。
Gorm中的Model,不仅仅是结构体本身,它是一组收集了对该数据集进行处理的操作集,换而言之是状态+算法。

在使用Gorm的时候 ,我们只是完成对结构体数据声明,用gorm特制标签(内部通过Go类型与值反射API来间接创建原始建表语句)实现迁移,或者是结构体部分数据填充当作查询条件,进行 curd 操作。当然,都得向Gorm提供结构体指针变量,以便gorm内部可寻值,将值数据填充到声明的结构体变量,从而无需使用传统显性方法返回形式获取数据结果集。

需要注意的是Go语言是面向接口编程,换而言之,其基于组合的继承,一个结构体A内嵌另外一个结构体B,你并不能断言A的实例是B类型,这与传统的面向对象编程A继承B,A的实例既是A类型也是B类型有所不同。但的若内嵌接口类型B,那可断言A的实例变量即是A结构体类型,也是B接口类型,实现接口多态。这一点,有使用Iris经验的同学,应该深有体会(iris.context本身是接口类型,gin.Context,gorm.Model是结构体)。

Association

使用automigrate 进行迁移,关联关系会被忽略,需要在其执行之后手动 AddForeignKey 添加外键

结构体中声明的关联关系字段需与外键指向的字段保持一致,但关联关系字段不会加入数据库关系主表结构。
以places表关联towns表示例,places结构体持有的Town类型字段不会被视作Place表中的字段,它只是显性的定义了一个关联关系字段,需要Association方法使用查询构建器指定待填充的属性字段,然后用Find方法填充该类型字段,以方便place数据嵌套显示,主要代码执行呈现如下

Gorm 关联模型详解

关联是用来定义结构体(或模型)间是如何进行交互

One2one

结构体

Place.go
package model
import ()
type Place struct {
 ID int `gorm:primary_key`
 Name string
 Town Town
 TownId int `gorm:"ForeignKey:id"` //this foreignKey tag didn't works
}

Town.go
package model
import ()
type Town struct {
 ID int `gorm:"primary_key"`
 Name string
}

一对一

package main

import (
        _ "database/sql"
        "fmt"
        "log"
        "pardon/relationship/one2one/model"

        _ "github.com/go-sql-driver/mysql"
        "github.com/jinzhu/gorm"
)

func main() {
        Db, err := gorm.Open("mysql", "root:secret@tcp(127.0.0.1:3306)/godb?charset=utf8&parseTime=True")
        defer Db.Close()
        if err != nil {
                log.Fatal(err)
        }

        Db.DropTableIfExists(&model.Place{}, &model.Town{})

        Db.AutoMigrate(&model.Place{}, &model.Town{})
        // AutoMigrate 会忽略外键,需手动添加
        // 参数分别为模型外键,关联表主键,删除级联,修改级联
        Db.Model(&model.Place{}).AddForeignKey("town_id", "towns(id)", "CASCADE", "CASCADE")

        t1 := model.Town{
                Name: "Pune",
        }
        t2 := model.Town{
                Name: "Mumbai",
        }
        t3 := model.Town{
                Name: "Hyderabad",
        }

        p1 := model.Place{
                Name: "Katraj",
                Town: t1,
        }
        p2 := model.Place{
                Name: "Thane",
                Town: t2,
        }
        p3 := model.Place{
                Name: "Secundarabad",
                Town: t3,
        }

        // 添加关联数据
        Db.Debug().Save(&p1)
        Db.Debug().Save(&p2)
        Db.Debug().Save(&p3)

        // 触发数据库级联删除 
        Db.Debug().Where("name=?", "Hyderabad").Delete(&model.Town{})

        // 属性修改
        Db.Debug().Model(&model.Place{}).Where("id=?", 1).Update("name", "Shivaji  Nagar")

        // 查询
        places := model.Place{}
        towns := model.Town{}
        fmt.Println("Before Association", places)
        Db.Debug().Where("name=?", "Shivaji  Nagar").Find(&places)
        fmt.Println("After Assocciation", places)

        err = Db.Debug().Model(&places).Association("town").Find(&places.Town).Error
        fmt.Println("After Association", towns, places)
        fmt.Println("After Association", towns, places, err)

}

One2many

Asscociation 惰性加载没同,preload 是主动预加载,它会主动加载尽可能多的数据。

Gorm 关联模型详解

package main

import (
        "fmt"

        _ "github.com/go-sql-driver/mysql"
        "github.com/jinzhu/gorm"
)

type Customer struct {
        CustomerID   int `gorm:"primary_key"`
        CustomerName string
        Contacts     []Contact `gorm:"ForeignKey:CustId"`
}

type Contact struct {
        ContactID   int `gorm:"primary_key"`
        CountryCode int
        MobileNo    uint
        CustId      int
}

func main() {
        db, err := gorm.Open("mysql", "root:secret@tcp(127.0.0.1:3306)/godb?charset=utf8&parseTime=True")
        if err != nil {
                panic(err.Error())
        }
        defer db.Close()

        db.DropTableIfExists(&Contact{}, &Customer{})
        db.AutoMigrate(&Customer{}, &Contact{})
        db.Model(&Contact{}).AddForeignKey("cust_id", "customers(customer_id)", "CASCADE", "CASCADE")

        Custs1 := Customer{CustomerName: "John", Contacts: []Contact{
                {CountryCode: 91, MobileNo: 956112},
                {CountryCode: 91, MobileNo: 997555}}}

        Custs2 := Customer{CustomerName: "Martin", Contacts: []Contact{
                {CountryCode: 90, MobileNo: 808988},
                {CountryCode: 90, MobileNo: 909699}}}

        Custs3 := Customer{CustomerName: "Raym", Contacts: []Contact{
                {CountryCode: 75, MobileNo: 798088},
                {CountryCode: 75, MobileNo: 965755}}}

        Custs4 := Customer{CustomerName: "Stoke", Contacts: []Contact{
                {CountryCode: 80, MobileNo: 805510},
                {CountryCode: 80, MobileNo: 758863}}}

        db.Debug().Create(&Custs1)
        db.Debug().Create(&Custs2)
        db.Debug().Create(&Custs3)
        db.Debug().Create(&Custs4)

        customers := &Customer{}
        //contacts := &Contact{}

        db.Debug().Where("customer_name=?", "Martin").Preload("Contacts").Find(&customers)
        fmt.Println("Customers", customers)

        db.Debug().Model(&Contact{}).Where("cust_id=?", 3).Update("country_code", 77)

        db.Debug().Where("customer_name=?", customers.CustomerName).Delete(&customers)
        fmt.Println("After Delete", customers)
}

Many2many

使用gorm的many2many 结构体标签,同样用Assocation加载关联关系,自动迁移,手动绑定生成外键

Gorm 关联模型详解

源码

package main

import (
        _ "database/sql"
        "fmt"
        "log"

        _ "github.com/go-sql-driver/mysql"
        "github.com/jinzhu/gorm"
)

type UserL struct {
        gorm.Model
        Uname     string
        Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
        gorm.Model
        Name string
}

type UserLanguages struct {
        UserLId    uint
        LanguageId uint
}

func main() {

        db, err := gorm.Open("mysql", "root:secret@(127.0.0.1:3306)/godb?charset=utf8&parseTime=True")
        if err != nil {
                log.Fatal(err)
        }
        defer db.Close()

        db.DropTableIfExists(&UserLanguages{}, &Language{}, &UserL{})
        db.AutoMigrate(&UserL{}, &Language{}, &UserLanguages{})

        db.Model(UserLanguages{}).AddForeignKey("user_l_id", "user_ls(id)", "CASCADE", "CASCADE")
        db.Model(UserLanguages{}).AddForeignKey("language_id", "languages(id)", "CASCADE", "CASCADE")

        langs := []Language{{Name: "English"}, {Name: "French"}}
        user1 := UserL{Uname: "John", Languages: langs}
        user2 := UserL{Uname: "Martin", Languages: langs}
        user3 := UserL{Uname: "Ray", Languages: langs}
        db.Debug().Save(&user1) //save is happening
        db.Debug().Save(&user2)
        db.Debug().Save(&user3)

        fmt.Println("After Saving Records")
        fmt.Println("User1", &user1)
        fmt.Println("User2", &user2)
        fmt.Println("User3", &user3)

        user := &UserL{}
        db.Debug().Where("uname=?", "Ray").Find(&user)
        err = db.Debug().Model(&user).Association("Languages").Find(&user.Languages).Error
        fmt.Println("User is now comming", user, err)

        fmt.Println(user, "to delete")
        db.Debug().Where("uname=?", "John").Delete(&user)

        db.Debug().Model(&UserL{}).Where("uname=?", "Ray").Update("uname", "Martin")
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
pardon110
讨论数量: 1

Association和Preload区别是啥呢?

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
开发者 @ 社科大
文章
134
粉丝
24
喜欢
101
收藏
55
排名:106
访问:8.9 万
私信
所有博文
社区赞助商