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 协议》,转载必须注明作者和本文链接
推荐文章: