在使用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.