编写复杂的参数化查询

参数化查询是根据传入的请求参数动态构建的查询。构建复杂的数据库查询可能具有挑战性,但您可以通过遵循本文中介绍的建议来获得更好的结果。

分而治之

第一个也是最重要的建议是将整个过程分解为独立的步骤。

解析请求参数

您需要做的第一件事是创建一个数据结构来保存传入的参数,例如

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)
	}
}