解决Gorm中使用Count后关联查询的问题

2,609 阅读2分钟

解决Gorm中使用Count后关联查询失效的问题

问题描述

当我们 在go中使用gorm进行多表join关联查询的时候

如果还有分页的需求

那么可能会是这样写

package main

import (
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   ormLogger "gorm.io/gorm/logger"
   "time"
)

func main() {

   type Detail struct {
      UesrId int    `json:"user_id"` // 自增 id
      Age    int    `json:"age"`     // 年龄
      Email  string `json:"email"`   // 邮箱
   }
   type User struct {
      Id     int    `json:"id" gorm:"primaryKey"` // 自增 id
      Name   string `json:"name"`                 // 名字
      Detail `gorm:"foreignKey:UesrId"`
   }

   type MysqlConfig struct {
      MysqlUrl     string
      Logger       ormLogger.Writer
      MaxIdleCount int
      MaxOpen      int
      MaxLifetime  time.Duration
      LogLevel     ormLogger.LogLevel
   }

   var c MysqlConfig

   DB, err := gorm.Open(mysql.Open(c.MysqlUrl))
   if err != nil {
      panic("GORM 连接失败," + err.Error())
   }

   tx := DB.Model(&User{}).Joins("Detail")

   var count int64
   tx.Count(&count)
   var data []User
   tx.Limit(GetLimit()).Offset(GetOffset()).Find(&data)
   
}

这样count会计算出值, 而 再查询数据就 会出现数据为空的情况

问题分析:

通过查询Count方法的源码我们发现

tx.Statement.AddClause(clause.Select{Expression: clause.Expr{SQL: "count(*)"}})

这里如果调用count方法,gorm会把你的sql的select的字段转换成 count* 所以,通过join关联查询的方式不可以进行对应字段的映射了

解决思路

我们执行查询和执行记数的tx,使用两个就好了 因为go语言是引用类型传递,所以该怎么进行拷贝tx对象呢

查询gorm相关源码发现,session()的源码里包含

// Session create new db session
func (db *DB) Session(config *Session) *DB {
   var (
      txConfig = *db.Config
      tx       = &DB{
         Config:    &txConfig,
         Statement: db.Statement,
         Error:     db.Error,
         clone:     1,
      }
   )

    if config.Context != nil || config.PrepareStmt || config.SkipHooks {
       tx.Statement = tx.Statement.clone()
       tx.Statement.DB = tx
    }
func (stmt *Statement) clone() *Statement {

    copy(newStmt.Joins, stmt.Joins)
    ...
    // 在这里执行了copy方法
}

所以我们可以利用gorm中的session功能深拷贝一个 tx对象,即:tx2 := tx.session()

package main

import (
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   ormLogger "gorm.io/gorm/logger"
   "time"
)

func main() {

   type Detail struct {
      UesrId int    `json:"user_id"` // 自增 id
      Age    int    `json:"age"`     // 年龄
      Email  string `json:"email"`   // 邮箱
   }
   type User struct {
      Id     int    `json:"id" gorm:"primaryKey"` // 自增 id
      Name   string `json:"name"`                 // 名字
      Detail `gorm:"foreignKey:UesrId"`
   }

   type MysqlConfig struct {
      MysqlUrl     string
      Logger       ormLogger.Writer
      MaxIdleCount int
      MaxOpen      int
      MaxLifetime  time.Duration
      LogLevel     ormLogger.LogLevel
   }

   var c MysqlConfig

   DB, err := gorm.Open(mysql.Open(c.MysqlUrl))
   if err != nil {
      panic("GORM 连接失败," + err.Error())
   }

   tx := DB.Model(&User{}).Joins("Detail")
    
   
   tx2 := tx.Session(&gorm.Session{})
   var count int64
   tx2.Count(&count)
   var data []User
   tx.Limit(GetLimit()).Offset(GetOffset()).Find(&data)
   
}

这样通过gorm中的session可以深拷贝出一个 gorm对象 执行不会受影响