在使用GORM的创建foreignKey关系的时,不管是按照官方文档给的例子写,还是说加上`gorm:"foreignKey:ID;references:UserId;"` 这样的,都是一样报错:define a valid foreign key for ... ,网上大部分给出的解决方案是gorm后面直接用"-"。如下图
type User struct {
gorm.Model
UserName string `json:"user_name" gorm:"comment:用户名"`
Blogs []Blog` json:"blogs" gorm:"-"`
}
type Blog struct {
gorm.Model
Title string `json:"title "gorm:"comment:标题"`
Content string `json:"Content "gorm:"comment:正文"`
UserId uint `json:"user_id "gorm:"comment:作者ID"`
}
这样确实可以绕过这个错误,但是官方提供的预加载(Preload)等高级方法用不了,
完美解决办法:
我们可以将需要创建外键的表拆开来写,还是上面的例子,我们可以这样写
type BaseUser struct {
gorm.Model
UserName string `json:"user_name" gorm:"comment:用户名"`
}
// 设置表名
func (BaseUser) TableName() string {
return "user"
}
type User struct {
BaseUser
Blogs []Blog` json:"blogs" gorm:"foreignKey:Id;references:UserId;"`
}
// 设置表名
func (User) TableName() string {
return "user"
}
type Blog struct {
gorm.Model
Title string `json:"title "gorm:"comment:标题"`
Content string `json:"Content "gorm:"comment:正文"`
UserId uint `json:"user_id "gorm:"comment:作者ID"`
}
这样我们在执行db.Debug().Migrator().AutoMigrate()创建表的时候创建 BaseUser,但是在代码中查询的时候使用User;如下图,可以查询出所有的用户,并且每个用户的所有blog也会一次性查询出来,避免了写原生SQL查询或者要多次循环才能查询出每个用户的所有blog
func FindUser(db *gorm.DB) error {
var (
err error
users = []User{}
)
err = db.Debug().
Model(&User{}).
Preload("Blogs"). //这里要注意
Find(&users).
Error
if err != nil {
log.Errorf("db error: %s", err)
return err
}
return err
}
同样如果需要在查询Blog列表的时候想要直接查出作者的信息,就需要在Blog表中加上user,如下图:
type BaseUser struct {
gorm.Model
UserName string `json:"user_name" gorm:"comment:用户名"`
}
// 设置表名
func (BaseUser) TableName() string {
return "user"
}
type User struct {
BaseUser
Blogs []BaseBlog `json:"blogs" gorm:"foreignKey:Id;references:UserId"`
}
// 设置表名
func (User) TableName() string {
return "user"
}
type BaseBlog struct {
gorm.Model
Title string `json:"title "gorm:"comment:标题"`
Content string `json:"Content "gorm:"comment:正文"`
UserId uint `json:"user_id "gorm:"comment:作者ID"`
}
// 设置表名
func (BaseBlog) TableName() string {
return "blog"
}
type Blog struct {
BaseBlog
User User `json:"user" gorm:"foreignKey:UserId;references:Id;"`
}
// 设置表名
func (Blog) TableName() string {
return "blog"
}
查询blog列表的时候也是同样操作,就可以同时查询出User的字段
func FindUser(db *gorm.DB) error {
var (
err error
Blogs = []Blog{}
)
err = db.Debug().
Model(&Blog{}).
Preload("User").
Find(&Blogs ).
Error
if err != nil {
log.Errorf("db error: %s", err)
return err
}
return err
}
当然还有其他很多高级的用法,比如在查询用户列表时候除了可以同时查询出该用户的所有blog,还可以对blog进行统计,需要稍微改一下User表结构,并使用GORM钩子,如下
type BaseUser struct {
gorm.Model
UserName string `json:"user_name" gorm:"comment:用户名"`
}
// 设置表名
func (BaseUser) TableName() string {
return "user"
}
type User struct {
BaseUser
Blogs []BaseBlog `json:"blogs" gorm:"foreignKey:Id;references:UserId"`
BlogsCount int `json:"blogs_count" gorm:"-"` //这样的只是为了返回给前端,没必要创建数据库字段,所以增加了gorm:"-"
}
// 设置表名
func (User) TableName() string {
return "user"
}
// 利用GORM钩子对每个用户的blog进行统计
func (e *User) AfterFind(_ *gorm.DB) error {
e.BlogsCount = len(e.Blogs)
return nil
}
type BaseBlog struct {
gorm.Model
Title string `json:"title "gorm:"comment:标题"`
Content string `json:"Content "gorm:"comment:正文"`
UserId uint `json:"user_id "gorm:"comment:作者ID"`
}
// 设置表名
func (BaseBlog) TableName() string {
return "blog"
}
type Blog struct {
BaseBlog
User User `json:"user" gorm:"foreignKey:UserId;references:Id;"`
}
// 设置表名
func (Blog) TableName() string {
return "blog"
}
这样可以完美解决建表的错误,还能使用Preload等高级方法,并且我们知道如果数据库真的创建外键之后有很多数据库约束,维护起来很麻烦,这样做数据库中还不会创建外键,非常完美的解决多个问题。更多GORM高级用法可以参考官方文档GORM Guides | GORM - The fantastic ORM library for Golang, aims to be developer friendly.