一个关于 GORM 的奇怪问题

需求

一般在些查询的时候都需要一个简单的 Filer 功能,根据传入的参数做一个系统的筛选

  • Code

    //	获取文章分类列表
    func GetCategoryList(condition map[string]string) *[]mysql.ArticleCategory {
    	category_list := []mysql.ArticleCategory{}
    	//	根据传入的参数先筛选一遍数据
    	db := filterArticleCategory(condition)
    
    	db.Table("blog_article_group").
    		Select("" +
    			"blog_article_group.id, " +
    			"blog_article_group.name," +
    			"count(blog_article.id) AS count, " +
    			"blog_article_group.created_at, " +
    			"blog_article_group.updated_at").
    		Joins("left join blog_article on blog_article_group.id = blog_article.g_id").
    		Group("blog_article_group.id").
    		Find(&category_list)
    
    	return &category_list
    }
    //	更具筛选的条件先设置筛选条件
    func filterArticleCategory(condition map[string]string) *gorm.DB {
    	if _, ok := condition["name"]; ok && condition["name"] != "" {
    		db = db.Where("name like ?", "%"+condition["name"]+"%")
    	}
    	return db
    }
    

查询结果

  • 第一次

    curl http://127.0.0.1:9000/article_category
    
    SELECT
    	blog_article_group.id,
    	blog_article_group.NAME,
    	count( blog_article.id ) AS count,
    	blog_article_group.created_at,
    	blog_article_group.updated_at
    FROM
    	`blog_article_group`
    	LEFT JOIN blog_article ON blog_article_group.id = blog_article.g_id
    GROUP BY
    	blog_article_group.id
    
  • 第二次

    curl http://127.0.0.1:9000/article_category?name=la
    
    SELECT
    	blog_article_group.id,
    	blog_article_group.NAME,
    	count( blog_article.id ) AS count,
    	blog_article_group.created_at,
    	blog_article_group.updated_at
    FROM
    	`blog_article_group`
    	LEFT JOIN blog_article ON blog_article_group.id = blog_article.g_id
    WHERE
    	( NAME LIKE '%la%' )
    GROUP BY
    	blog_article_group.id
    
  • 第三次

    curl http://127.0.0.1:9000/article_category?name=la
    
    SELECT
    	blog_article_group.id,
    	blog_article_group.NAME,
    	count( blog_article.id ) AS count,
    	blog_article_group.created_at,
    	blog_article_group.updated_at
    FROM
    	`blog_article_group`
    	LEFT JOIN blog_article ON blog_article_group.id = blog_article.g_id
    WHERE
    	( NAME LIKE '%la%' )
    	AND ( NAME LIKE '%la%' )
    GROUP BY
    	blog_article_group.id
    
  • 第四次

    curl http://127.0.0.1:9000/article_category?name=la
    
    SELECT
    	blog_article_group.id,
    	blog_article_group.NAME,
    	count( blog_article.id ) AS count,
    	blog_article_group.created_at,
    	blog_article_group.updated_at
    FROM
    	`blog_article_group`
    	LEFT JOIN blog_article ON blog_article_group.id = blog_article.g_id
    WHERE
    	( NAME LIKE '%la%' )
    	AND ( NAME LIKE '%la%' )
    	AND ( NAME LIKE '%la%' )
    GROUP BY
    	blog_article_group.id
    

问题分析

在多次查询的时候 filer 函数中的 where 语句会执行多次,最终导致出现多个 AND.

  • 每个请求都是一个 goruntime,其中的 map 也是独立的不会出现冲突
  • GORM 在版本之后返回的 db 都是指针,已经做好了全局的连接池管理

没有看过 GORM 的源码,初步估计是全局返回的 db 指针的问题。

Gorm implements method chaining interface, so you could write code like this:

db, err := gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable")

// create a new relation
tx := db.Where("name = ?", "jinzhu")

// add more filter
if someCondition {
    tx = tx.Where("age = ?", 20)
} else {
    tx = tx.Where("age = ?", 30)
}

if yetAnotherCondition {
    tx = tx.Where("active = ?", 1)
}

Query won’t be generated until a immediate method, which could be useful in some cases.

Like you could extract a wrapper to handle some common logic

问题

因为使用了全局的 db 指针进行查询,但是内部会共享一个 Scope 导致查询条件重复。官方也给出了方案,就是在操作时需要将全局的 db 复制一份。

func GetCategoryList(condition map[string]string) *[]ArticleCategory {
	category_list := []ArticleCategory{}
	tx := filterArticleCategory(condition)

	tx.Table("blog_article_group").
		Select("" +
			"blog_article_group.id, " +
			"blog_article_group.name," +
			"count(blog_article.id) AS count, " +
			"blog_article_group.created_at, " +
			"blog_article_group.updated_at").
		Joins("left join blog_article on blog_article_group.id = blog_article.g_id").
		Group("blog_article_group.id").
		Find(&category_list)

	return &category_list
}

func filterArticleCategory(condition map[string]string) *gorm.DB {
	tx := db

	if _, ok := condition["name"]; ok && condition["name"] != "" {
		tx = db.Where("name like ?", "%"+condition["name"]+"%")
	}
	return tx
}