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
开发者 @ 社科大
文章
134
粉丝
24
喜欢
103
收藏
56
排名:105
访问:8.9 万
私信
所有博文
社区赞助商