ClausesScopes

方案一(不考虑):通过文本拼接SQL

具体示例可查阅:
https://www.cnblogs.com/haima/p/13335189.html
这种方法自由度很高,但也有以下两个明显的缺点:

  • 有安全隐患,直接拼接SQL的方式,很容易留下SQL注入的安全隐患,影响系统安全。
  • 拼接语句顺序受限,通过手动拼接SQL的方式,需考虑拼接语句的顺序,例如,JOIN语句不能直接任意放到where语句之后,而需确保在where语句之前等限制,不好灵活地实现动态调整。

方案二(推荐):使用Scopes\Clause实现动态拼接

通过查阅GORM官方文档发现了Scopes\Clause两个可用于实现动态操作的方法,但没有现成可直接用于参考的实战例子和相关详细说明,遂翻阅了下相关方法的源代码,得出如下的实践。

我们直接来举个例子。
在这里插入图片描述

假设有如下存储结构:

//学生信息
type Student struct {
	gorm.Model
	Name      string
	ClassName string
}

//学生家庭成员信息
type FamilyMember struct {
	gorm.Model
	StudentId    uint
	Name         string
}

学生家庭成员信息(FamilyMember)跟学生信息(Student)存储在两张不同的表。
我们需要根据不同的业务条件动态决定是否需要通过学生家庭成员信息查询到对应的学生信息。
从系统性能角度考虑,若当无需通过学生家庭成员信息进行查询时,则很明显我们无需JOIN多一张表以及加多对应的查询条件,那么我们可以通过以下方式实现动态的JOIN表以及动态查询条件:

func withFilterFamilyMemberJoin() func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		db = db.Joins("INNER JOIN family_members ON students.id = family_members.student_id")
		return db
	}
}

func withFilterFamilyNameWhere(name string) clause.Expr {
	return gorm.Expr("family_members.name like ?", "%"+name+"%")
}

//是否需要根据家庭成员姓名搜索
func isNeedSearchByFamilyName() bool {
	return false
}

func main() {
	db, err := gorm.Open(sqlite.Open("test.db"))
	if err != nil {
		panic("failed to connect database")
	}
	db.Logger = db.Logger.LogMode(logger.Info) //开启执行SQL日志打印

	searchKeyword := "张三"
	var student Student

	queryDB := db.Select("students.id, students.name")
	whereClause := gorm.Expr("students.name like ?", "%"+searchKeyword+"%")

	if isNeedSearchByFamilyName() {
		queryDB.Scopes(withFilterFamilyMemberJoin())
		whereClause = gorm.Expr("? or ?", whereClause, withFilterFamilyNameWhere(searchKeyword))
	}
	
	queryDB.Clauses(whereClause)

	queryDB.First(&student)
	fmt.Println(student)
}
isNeedSearchByFamilyNamefalse
SELECT students.id, students.name 
FROM `students` 
WHERE students.name like "%张三%" 
AND `students`.`deleted_at` IS NULL 
ORDER BY `students`.`id`
LIMIT 1
isNeedSearchByFamilyNametrue
SELECT students.id, students.name
FROM `students` 
INNER JOIN family_members ON students.id = family_members.student_id 
WHERE (students.name like "%张三%" or family_members.name like "%张三%")
AND `students`.`deleted_at` IS NULL 
ORDER BY `students`.`id` 
LIMIT 1

结合代码与结果可以看出,我们实现了动态join表及动态变更查询条件的目的,在保证系统安全的前提下,也能够更好地抽出灵活可复用的SQL查询方法。

官方相关用法说明:

总结

本文通过阅读源码、实践分享了一种GORM常见场景动态操作的方案。若我们平时在使用成熟的第三方库遇到疑问时,可尝试多阅读官方文档和搜索网上的相关资料,如果没有很好可参考的解决方案,则可以考虑多思考底层逻辑,例如通过翻阅源代码的方式,思考、学习相关实现原理及逻辑,以便找到一些在官方文档中未详细说明的方案,同时通常也能够学习到相关的一些思路精髓。

最后简单总结下两种用法:

ClausesScopes