GoooQo是一款动态查询语言的Golang引擎,支持通过对象直接构建查询语句,特别是通过查询对象动态构建查询语句。它通过反射技术读取查询字段的名称和赋值,实现动态生成查询条件,从而简化开发工作。

为了优化性能,GoooQo 推出了代码生成工具 gooogen。该工具通过抽象语法树(AST)技术读取查询字段名称,自动生成查询语句构建方法,提供了一种替代反射技术构建动态查询的方法。

反射技术

在GoooQo中,开发者只需定义一个实体对象和一个包含有查询字段的查询对象,即可完成单表的增删查改操作。以查询对象UserQuery为例,该结构体包含了多个可选的查询条件字段:

type UserQuery struct {
  PageQuery
  Name        *string
  NameContain *string
  AgeGt       *int
  AgeLt       *int
  UserOr      *UserQuery
  AgeLtAvg    *UserQuery `select:"avg(age)" from:"t_user"`
}

谓词后缀字段的名称由属性和谓词组成,通过反射技术读取字段名称后即可直接映射为SQL查询条件,如NameContain表示name LIKE ?AgeGt表示age > ?

逻辑后缀字段的名称包括逻辑连接词AndOr,用于指定多个
查询条件的连接方式,如UserOr

子查询字段通过名称构建基础条件,通过标签和赋值构建子查询条件,如AgeLtAvg

GoooQo通过反射技术为每个字段构建查询条件,并根据字段赋值确定组合的查询条件。但是这也带来了一定的性能问题。

代码生成技术

为了消除反射引入的性能问题,GoooQo推出了一款代码生成工具gooogen,可以自动为用户定义的查询对象生成对应的动态查询构建代码,大大提高了代码的编写效率。

首先,通过下面这条命令安装gooogen:

go install github.com/doytowin/goooqo/gooogen@latest

然后,在查询对象的定义上添加注释:

//go:generate gooogen -type sql -o user_query_builder.go
type UserQuery struct {
  //...
}

最后,执行go generate命令便会生成一个user_query_builder.go文件,其中包含了根据UserQuery结构体自动构建SQL查询条件的方法:

func (q UserQuery) BuildConditions() ([]string, []any) {
  conditions := make([]string, 0, 4)
  args := make([]any, 0, 4)
  if q.Name != nil {
    conditions = append(conditions, "name = ?")
    args = append(args, *q.Name)
  }
  if q.NameContain != nil {
    conditions = append(conditions, "name LIKE ?")
    args = append(args, "%"+*q.NameContain+"%")
  }
  if q.AgeGt != nil {
    conditions = append(conditions, "age > ?")
    args = append(args, *q.AgeGt)
  }
  if q.AgeLt != nil {
    conditions = append(conditions, "age < ?")
    args = append(args, *q.AgeLt)
  }
  if q.UserOr != nil {
    cond, args0 := BuildConditions(q.UserOr, "(", " OR ", ")")
    conditions = append(conditions, cond)
    args = append(args, args0...)
  }
  if q.AgeLtAvg != nil {
    where, args0 := BuildConditions(q.AgeLtAvg, " WHERE ", " AND ", "")
    conditions = append(conditions, "age < (SELECT avg(age) FROM t_user"+where+")")
    args = append(args, args0...)
  }
  return conditions, args
}

这段代码会根据UserQuery对象的赋值构建一组查询条件以及对应的参数,通过WHERE和AND连接便能得到对应的查询子句,进而构建完整的查询语句并执行。

query := UserQuery{Name: P("Tim"), UserOr: &UserQuery{AgeGt: P(10), AgeLt: P(20)}}
where, args := rdb.BuildWhereClause(query)
// where: WHERE name = ? AND (age > ? OR age < ?)
            
query := UserQuery{AgeGt: P(10), AgeLtAvg: &UserQuery{NameContain: P("J")}}
where, args := rdb.BuildWhereClause(query)
// where: WHERE age > ? AND age < (SELECT avg(age) FROM t_user WHERE name LIKE ?)

通过对比UserQuery对象和生成的BuildConditions方法可知,
UserQuery对象中每添加一个字段,就需要在BuildConditions方法中对应添加4行代码用于查询条件的拼接,代码量对比为1比4,生成的代码数量占了80%。再加上围绕BuildConditions方法封装的代码,GoooQo可以帮助减少至少80%以上的数据库访问代码。

总结

GoooQo的代码生成工具gooogen通过自动化生成动态查询构建代码,有效替代了反射技术,既保证了高效运行性能,又大幅提升了开发效率。开发者只需定义查询对象,便可自动生成复杂的查询条件构建器,在保证性能的同时,大幅提升开发效率。