编写复杂的参数化查询
参数化查询是根据传入的请求参数动态构建的查询。构建复杂的数据库查询可能具有挑战性,但您可以通过遵循本文中介绍的建议来获得更好的结果。
分而治之
第一个也是最重要的建议是将整个过程分解为独立的步骤。
解析请求参数
您需要做的第一件事是创建一个数据结构来保存传入的参数,例如
type ArticleFilter struct {
CategoryID int64
Search string
Page int
}
以及一个工厂方法,该方法将从 http.Request
或 JSON 负载中解析参数
func articleFilterFromRequest(req *http.Request) (*ArticleFilter, error) {
query := req.URL.Query()
f := new(ArticleFilter)
f.Search = query.Get("search")
categoryID, err := strconv.ParseInt(query.Get("category_id"), 10, 64)
if err != nil {
return nil, err
}
f.CategoryID = categoryID
page, err := strconv.Atoi(query.Get("page"))
if err != nil {
return nil, err
}
f.Page = page
return f, nil
}
参数验证
此步骤的目的是确保您拥有足够的数据来构建查询或设置默认值。
func (f *ArticleFilter) Validate() error {
if f.CategoryID == 0 {
return errors.New("category id is required")
}
if f.Page == 0 {
f.Page = 1
} else f.Page > 1000 {
return errors.New("you can't paginate past page #1000")
}
return nil
}
查询生成
在此步骤中,您拥有足够的数据可以使用 Bun API 构建查询。最好将所有查询生成逻辑保留在一个方法中,以便于跟踪。
func articleFilterQuery(q *bun.SelectQuery, f *ArticleFilter) (*bun.SelectQuery, error) {
q = q.Where("category_id = ?", f.CategoryID).
Limit(10).
Offset(10 * (f.Page - 1))
if f.Search != "" {
q = q.Where("title LIKE ?", "%"+f.Search+"%")
}
return q, nil
}
查询执行
最后,您需要执行生成的查询,并可选地进行一些后处理。最终结果可能如下所示
func handler(w http.ResponseWriter, req *http.Request) {
f, err := articleFilterFromRequest(req)
if err != nil {
panic(err)
}
if err := f.Validate(); err != nil {
panic(err)
}
var articles []Article
q, err := articleFilterQuery(db.NewSelect().Model(&articles), f)
if err != nil {
panic(err)
}
if err := q.Scan(req.Context()); err != nil {
panic(err)
}
if err := json.NewEncoder(w).Encode(map[string]interface{}{
"articles": articles,
}); err != nil {
panic(err)
}
}