美文网首页
【Go Web开发】查询结果分页

【Go Web开发】查询结果分页

作者: Go语言由浅入深 | 来源:发表于2022-02-24 22:40 被阅读0次

如果您的接口返回一个包含数百或数千条记录的列表,那么出于性能或可用性的考虑,您可能需要在接口上实现分页—以便在单个HTTP响应中只返回部分数据条目。

为了说明这个特性,本节将更新GET /v1/movies接口,使它支持'pages'功能,客户端可以通过在请求中发送page和page_size参数来获取数据库中特定的movies列表,如下所示:

// 返回第一页的5条记录(1-5条数据)
/v1/movies?page=1&page_size=5

// 返回第二页的5条数据(6-10条数据
/v1/movies?page=2&page_size=5

基本上,改变page_size参数将改变每个“页面”上显示的数据数量,将page参数增加1将显示列表中的下一个“页面”的数据。

LIMIT和OFFSET子句

要实现分页的话,最简单的方式就是在数据库查询的时候,在SQL查询语句中添加LIMITOFFSET子句。

LIMIT子句允许您设置SQL查询能返回记录数的最大值,而OFFSET允许您在查询记录返回之前“跳过”特定的行数。

在我们的应用程序中,只需要将客户端提供的page和page_size值转换为SQL查询中的LIMIT和OFFSET适当值。数学很简单:

LIMIT = page_size
OFFSET = (page - 1) * page_size

举个具体的例子,如果客户端发送如下请求:

 /v1/movies?page_size=5&page=3

我们需要将上面请求翻译成以下SQL查询:

SELECT id, created_at, title, year, runtime, genres, version
FROM movies
WHERE (to_tsvector('simple', title) @@ plainto_tsquery('simple', $1) OR $1 = '') 
AND (genres @> $2 OR $2 = '{}')
ORDER BY %s %s, id ASC
LIMIT 5 OFFSET 10

我们可以为Filters结构体添加帮助函数来计算LIMIT和OFFSET值。更新internal/data/filters.go文件。如下所示:

package data

...

func (f Filters)limit() int {
    return f.PageSize
}

func (f Filters)offset() int {
    return (f.Page -1) * f.PageSize
}

在offset函数中理论上可能会发生整数溢出,因为我们将两个整数相乘。还好前面做了校验,通过ValidateFilters()函数能控制两个整数相乘不溢出。page最大值为10000000,page_size最大值100。

更新数据库模型

最后,需要更新数据模型的GetAll()方法,将LIMIT和OFFSET子句添加到SQL查询中:

File:internal/data/movies.go


func (m MovieModel)GetAll(title string, genres []string, filters Filters) ([]*Movie, error)  {
    // 更新SQL查询,添加LIMIT和OFFSET子句
    query := fmt.Sprintf(`
        SELECT id, create_at, title, year, runtime, genres, version
        FROM movies
        WHERE (to_tsvector('simple', title) @@ plainto_tsquery('simple', $1) OR $1 = '') 
        AND (genres @> $2 OR $2 = '{}')
        ORDER BY %s %s, id ASC
        LIMIT $3 OFFSET $4`, filters.sortColumn(), filters.sortDirection())
    //创建3s超时上下文实例
    ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
    defer cancel()
    //因为SQL查询中有好几个占位符参数,所以我们用一个切片存放。注意我们调用limit()和offset()方法
    args := []interface{}{title, pq.Array(genres), filters.limit(), filters.offset()}
    rows, err := m.DB.QueryContext(ctx, query, args...)
    if err != nil {
        return nil, err
    }
       //其他代码不变
}

下面可以重启服务试试效果。我们发送一个带有page_siz=2的请求:

$ curl "localhost:4000/v1/movies?page_size=2"
{
        "movies": [
                {
                        "id": 1,
                        "title": "Moana",
                        "year": 2016,
                        "runtime": "107 mins",
                        "genres": [
                                "animation",
                                "adventure"
                        ],
                        "Version": 1
                },
                {
                        "id": 2,
                        "title": "Black Panther",
                        "year": 2018,
                        "runtime": "134 mins",
                        "genres": [
                                "sci-fi",
                                "action",
                                "adventure"
                        ],
                        "Version": 2
                }
        ]
}

看起来不错。接口返回数据库中的前两条movies记录。(使用默认排序方式,按照movie的ID升序排列)

我们再发送一个获取下一页movies的请求。如果你跟随本书操作的话,你将看到接口返回数据库中后续的记录:

$ curl "localhost:4000/v1/movies?page_size=2&page=2"
{
        "movies": [
                {
                        "id": 4,
                        "title": "The Breakfast Club",
                        "year": 1985,
                        "runtime": "97 mins",
                        "genres": [
                                "comedy",
                                "drama"
                        ],
                        "Version": 15
                }
        ]
}

如果你尝试请求第三页数据,将得到一个空的JSON数组响应结果:

$ curl "localhost:4000/v1/movies?page_size=2&page=3"
{
        "movies": null
}

相关文章

网友评论

      本文标题:【Go Web开发】查询结果分页

      本文链接:https://www.haomeiwen.com/subject/gxmtrrtx.html