Gorm 数据库迁

本文主要介绍了使用Gorm的一些避坑技巧,比如数据库迁移版本管理,查询关联,json嵌套,自定义主键等。

全局技巧

将数据库建表逻辑纳入代码版本管理,借助gomigrate 可方便做到。

AutoMigrate

AutoMigrate可以使用定义的结构体,自动创建与之匹配的数据库表结构,但有一个问题,它不支持外键,这意味着使用它建表时,关联关系会被忽略。可替代的方案是使用 CreateTable,用 gormigrate 处理,代码如下

package migrate

import (
    "time"

    "github.com/jinzhu/gorm"
    gormigrate "gopkg.in/gormigrate.v1"
)

type Article struct {
    gorm.Model
    Title string
    Slug  string `gorm:"unique_index"`
    Body  string
    Tags  []Tag `gorm:"many2many:article_tags;"`
}

type Tag struct {
    gorm.Model
    Name     string
    Articles []Article `gorm:"many2many:article_tags;"`
}

// Start 处理迁移管理
func Start(db *gorm.DB) error {
    m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{
        {
            ID: "initial",  // 迁移标签
            Migrate: func(tx *gorm.DB) error {
                return tx.CreateTable(&Article{}, &Tag{}).Error
            },
            Rollback: func(tx *gorm.DB) error {
                return tx.DropTable(&Article{}, &Tag{}).Error
            },
        },
    })
    return m.Migrate()
}

使用多对多关系gorm标签,意味着可以通过单个文章(或标签)获取它的所有标签(或文章)。

Gormigrate

迁移文件内置模型

在使用gormigrate时,为确保所有的迁移文件在执行时有序,通常将结构体声明直接定义在迁移记录内,目的在于保留历史记录版本,利于回滚,可以如下操作

func Start(db *gorm.DB) error {
    m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{
        {
            ID: "initial",
            Migrate: func(tx *gorm.DB) error {
                type Article struct {
                    gorm.Model
                    Title string
                    Slug  string `gorm:"unique_index"`
                    Body  string
                    Tags  []Tag `gorm:"many2many:article_tags;"`
                }
                type Tag struct {
                    gorm.Model
                    Name     string
                    Articles []Article `gorm:"many2many:article_tags;"`
                }
                return tx.CreateTable(&Article{}, &Tag{}).Error
            },
            Rollback: func(tx *gorm.DB) error {
                return tx.DropTable("articles", "tags").Error
            },
        },
    })
    return m.Migrate()
}

多驱动迁移

下面展示了,在使用sqlite3 驱动时,不执行迁移做法

{
    ID: "201609011303",
    Migrate: func(tx *gorm.DB) error {
        if tx.Dialect().GetName() == "sqlite3" {
            return nil
        }

        return tx.Table(
            "team_users",
        ).AddForeignKey(
            "team_id",
            "teams(id)",
            "RESTRICT",
            "RESTRICT",
        ).Error
    },
    Rollback: func(tx *gorm.DB) error {
        if tx.Dialect().GetName() == "sqlite3" {
            return nil
        }

        return gormigrate.ErrRollbackImpossible
    },
}

查询填充

同laravel的orm一样,gorm也可通过关联关系填充查询字段。只不过因为语言特性,go更偏爱使用结构体引用,而非等待方法或函数执行的返回结果。下面使用到了渴求式加载preload,理念与laravel一致。

// 渴求加载ID为1文章中的所有标签 
a := &Article{}
db.Preload("Tags").First(a, 1)
spew.Dump(a)

// 或查找带go标签的文章
t := &Tag{}
db.Preload("Articles").Where(&Tag{Name: "go"}).First(t)
spew.Dump(t)

在第二个查询中,会找到go标签的文章。但查找的文章结果集中没有标签数据,很奇怪的事。这种查询现象 同java中的maybits一样道理。幸运的是,Gorm 知道如何处理,只需要更进一步加载,如下所示。

// 查询带go标签的所有文章并且加载它们的标签数据
t := &Tag{}
db.Preload("Articles").Preload("Articles.Tags").Where(&Tag{Name: "go"}).First(t)

换句话而言,需要显式 的预加载内嵌数据

清理或删除关联关系

Gorm中的关联让人难以理解。比如 Preload(), Related() , Association() 有什么区别?无论如何,如果想从文章中删除所有标签,该如何做?

// 准备已经存在的文件章实例
a := &Article{}
db.First(a, 1)

// 清除关联关系
db.Model(a).Association("Tags").Clear()

上述代码会将对应多对表的外键设置为 NULL.
若想完全从数据库中删除记录,可用如下

db.Unscoped().Where(Tag{ArticleID: a.ID}).Delete(&Tag{})

注意 Gorm 模型中的Delete方法默认是执行的软删除,只是设置了删除时间,并不会真正删除数据库数据,真实删除要带上 Unscoped().

本作品采用《CC 协议》,转载必须注明作者和本文链接
pardon110
讨论数量: 1

model和migrations本来是两个东西呀,放在一起怎么用?

3年前 评论

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