接下来关于gf ORM提供的模型关联实现,从GF v1.13.6版本开始提供,目前属于实验性特性。

那么我们就使用一个例子来介绍gf ORM提供的模型关联吧。

为简化示例,我们这里设计得表都尽可能简单,每张表仅包含3-4个字段,方便阐述关联关系即可。

根据表定义,我们可以得知:

  1. 用户表与用户详情是1:1关系。
  2. 用户表与用户学分是1:N关系。
  3. 这里并没有演示N:N的关系,因为相比较于1:N的查询只是多了一次关联、或者一次查询,最终处理方式和1:N类似。

那么Golang的模型可定义如下:

  1. // 用户表
  2. type EntityUser struct {
  3. Uid int `orm:"uid"`
  4. Name string `orm:"name"`
  5. }
  6. // 用户详情
  7. type EntityUserDetail struct {
  8. Uid int `orm:"uid"`
  9. Address string `orm:"address"`
  10. }
  11. // 用户学分
  12. type EntityUserScores struct {
  13. Id int `orm:"id"`
  14. Uid int `orm:"uid"`
  15. Score int `orm:"score"`
  16. }
  17. // 组合模型,用户信息
  18. type Entity struct {
  19. User *EntityUser
  20. UserDetail *EntityUserDetail
  21. UserScores []*EntityUserScores
  22. }

其中,EntityUser, EntityUserDetail, EntityUserScores分别对应的是用户表、用户详情、用户学分数据表的数据模型。是一个组合模型,对应的是一个用户的所有详细信息。

查询单条模型数据比较简单,直接使用Scan方法即可,该方法会自动识别绑定查询结果到单个对象属性还是数组对象属性中。例如:

  1. // 定义用户列表
  2. var user Entity
  3. // 查询用户基础数据
  4. // SELECT * FROM `user` WHERE `name`='john'
  5. err := db.Table("user").Scan(&user.User, "name", "john")
  6. if err != nil {
  7. return err
  8. }
  9. // 查询用户详情数据
  10. // SELECT * FROM `user_detail` WHERE `uid`=1
  11. err := db.Table("user_detail").Scan(&user.UserDetail, "uid", user.User.Uid)
  12. // 查询用户学分数据
  13. // SELECT * FROM `user_scores` WHERE `uid`=1
  14. err := db.Table("user_scores").Scan(&user.UserScores, "uid", user.User.Uid)

该方法在之前的章节中已经有介绍,因此这里不再赘述。

多条数据记录

查询多条数据记录并绑定数据到数据模型数组中,需要使用到ScanList方法,该方法会需要用户指定结果字段与模型属性的关系,随后底层会遍历数组并自动执行数据绑定。例如:

这其中涉及到两个比较重要的方法:

1. ScanList

方法定义:

  1. // ScanList converts <r> to struct slice which contains other complex struct attributes.
  2. // Note that the parameter <listPointer> should be type of *[]struct/*[]*struct.
  3. //
  4. // type Entity struct {
  5. // User *EntityUser
  6. // UserDetail *EntityUserDetail
  7. // UserScores []*EntityUserScores
  8. // }
  9. // var users []*Entity
  10. // or
  11. // var users []Entity
  12. //
  13. // ScanList(&users, "User")
  14. // ScanList(&users, "UserDetail", "User", "uid:Uid")
  15. // ScanList(&users, "UserScores", "User", "uid:Uid")
  16. // The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct
  17. // that current result will be bound to.
  18. // The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
  19. // struct attribute name. It automatically calculates the HasOne/HasMany relationship with given <relation>
  20. // parameter.
  21. // See the example or unit testing cases for clear understanding for this function.
  22. func (m *Model) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error)
  • ScanList(&users, "User")

表示将查询到的用户信息数组数据绑定到users列表中每一项的属性上。

  • ScanList(&users, "UserDetail", "User", "uid:Uid")

表示将查询到用户详情数组数据绑定到users列表中每一项的UserDetail属性上,并且和另一个User对象属性通过uid:Uid字段:属性关联,内部将会根据这一关联关系自动进行数据绑定。其中uid:Uid前面的uid表示查询结果字段中的uid字段,后面的Uid表示目标关联对象中的Uid属性。

  • ScanList(&users, "UserScores", "User", "uid:Uid")

表示将查询到用户详情数组数据绑定到users列表中每一项的UserScores属性上,并且和另一个User对象属性通过uid:Uid字段:属性关联,内部将会根据这一关联关系自动进行数据绑定。由于UserScores是一个数组类型[]*EntityUserScores,因此该方法内部可以自动识别到UserUserScores其实是1:N的关系,自动完成数据绑定。

需要提醒的是,如果关联数据中对应的关联属性数据不存在,那么该属性不会被初始化并将保持nil

2. ListItemValues/ListItemValuesUnique

方法定义:

  • gdb.ListItemValuesUnique(users, "User", "Uid")用于获取users数组中,每一个User属性项中的Uid属性,构造成[]interface{}数组返回。这里以便根据uid构造成SELECT...IN...查询。