Golang如何用gorm实现分页的功能

2025-06-25 17:50
文章标签 实现 golang 功能 分页 gorm

本文主要是介绍Golang如何用gorm实现分页的功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Golang如何用gorm实现分页的功能》:本文主要介绍Golang如何用gorm实现分页的功能方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...

背景

在提供列表接口时一般要用到分页,对于存储在某些数据库中的数据进行分页起来非常的方便,下文给出一个通过gorm进行分页并通过http返回数据的例子。

go库下载

gorm官方文档地址:https://gorm.io/zh_CN/docs/,库下载如下:

go get -u gorm.io/gorm

本文使用的数据库是mysql,因此要下载mysql驱动:

go get -u gorm.io/driver/mysql

这里使用gin框架来提供查询列表的接口想,库下载如下:

go get -u github.com/gin-gonic/gin

初始化数据

【1】建表

建一个用来测试分页的表,sql如下:

CREATE TABLE test_page
(
    id          INT AUTO_INCREMENT PRIMARY KEY,
    number      INT NOT NULL,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT '测试分页表';

【2】插入数据

插入30条numbercreate_time字段不同的数据,sql如下:

INSERT INTO test_page (number, create_time)
VALUES (1, NOW() + INTERVAL 0 SECOND),
       (2, NOW() + INTERVAL 1 SECOND),
       (3, NOW() + INTERVAL 2 SECOND),
       (4, NOW() + INTERVAL 3 SECOND),
       (5, NOW() + INTERVAL 4 SECOND),
       (6, NOW() + INTERVAL 5 SECOND),
       (7, NOW() + INTERVAL 6 SECOND),
       (8, NOW() + INTERVAL 7 SECOND),
       (9, NOW() + INTERVAL 8 SECOND),
       (10, NOW() + INTERVAL 9 SECOND),
       (11, NOW() + INTERVAL 10 SECOND),
       (12, NOW() + INTERVAL 11 SECOND),
       (13, www.chinasem.cnNOW() + INTERVAL 12 SECOND),
       (14, NOW() + INTERVAL 13 SECOND),
       (15, NOW() + INTERVAL 14 SECOND),
       (16, NOW() + INTERVAL 15 SECOND),
       (17, NOW() + INTERVAL 16 SECOND),
       (18, NOW() + INTERVAL 17 SECOND),
       (19, NOW() + INTERVAL 18 SECOND),
       (20, NOW() + INTERVAL 19 SECOND),
       (21, NOW() + INTERVAL 20 SECOND),
       (22, NOW() + INTERVAL 21 SECOND),
       (23, NOW() + INTERVAL 22 SECOND),
       (24, NOW() + INTERVAL 23 SECOND),
       (25, NOW() + INTERVAL 24 SECOND),
       (26, NOW() + INTERVAL 25 SECOND),
       (27, NOW() + INTERVAL 26 SECOND),
       (28, NOW() + INTERVAL 27 SECOND),
       (29, NOW() + INTERVAL 28 SECOND),
       (30, NOW() + INTERVAL 29 SECOND);

【3】查看数据

查看创建出来的30条数据:

mysql [xxx]> select * from test_page;
+----+--------+---------------------+---------------------+
| id | number | create_time         | update_time         |
+----+--------+---------------------+---------------------+
|  1 |      1 | 2024-10-21 07:11:19 | 2024-10-21 07:11:19 |
|  2 |      2 | 2024-10-21 07:11:20 | 2024-10-21 07:11:19 |
|  3 |      3 | 2024-10-21 07:11:21 | 2024-10-21 07:11:19 |
|  4 |      4 | 2024-10-21 07:11:22 | 2024-10-21 07:11:19 |
|  5 |      5 | 2024-10-21 07:11:23 | 2024-10-21 07:11:19 |
|  6 |      6 | 2024-10-21 07:11:24 | 2024-10-21 07:11:19 |
|  7 |      7 | 2024-10-21 07:11:25 | 2024-10-21 07:11:19 |
|  8 |      8 | 2024-10-21 07:11:26 | 2024-10-21 07:11:19 |
|  9 |      9 | 2024-10-21 07:11:27 | 2024-10-21 07:11:19 |
| 10 |     10 | 2024-10-21 07:11:28 | 2024-10-21 07:11:19 |
| 11 |     11 | 2024-10-21 07:11:29 | 2024-10-21 07:11:19 |
| 12js |     12 | 2024-10-21 07:11:30 | 2024-10-21 07:11:19 |
| 13 |     13 | 2024-10-21 07:11:31 | 2024-10-21 07:11:19 |
| 14 |     14 | 2024-10-21 07:11:32 | 2024-10-21 07:11:19 |
| 15 |     15 | 2024-10-21 07:11:33 | 2024-10-21 07:11:19 |
| 16 |     16 | 2024-10-21 07:11:34 | 2024-10-21 07:11:19 |
| 17 |     17 | 2024-10-21 07:11:35 | 2024-10-21 07:11:19 |
| 18 |     18 | 2024-10-21 07:11:36 | 2024-10-21 07:11:19 |
| 19 |     19 | 2024-10-21 07:11:37 | 2024-10-21 07:11:19 |
| 20 |     20 | 2024-10-21 07:11:38 | 2024-10-21 07:11:19 |
| 21 |     21 | 2024-10-21 07:11:39 | 2024-10-21 07:11:19 |
| 22 |     22 | 2024-10-21 07:11:40 | 2024-10-21 07:11:19 |
| 23 |     23 | 2024-10-21 07:11:41 | 2024-10-21 07:11:19 |
| 24 |     24 | 2024-10-21 07:11:42 | 2024-10-21 07:11:19 |
| 25 |     25 | 2024-10-21 07:11:43 | 2024-10-21 07:11:19 |
| 26 |     26 | 2024-10-21 07:11:44 | 2024-10-21 07:11:19 |
| 27 |     27 | 2024-10-21 07:11:45 | 2024-10-21 07:11:19 |
| 28 |     28 | 2024-10-21 07:11:46 | 2024-10-21 07:11:19 |
| 29 |     29 | 2024-10-21 07:11:47 | 2024-10-21 07:11:19 |
| 30 |     30 | 2024-10-21 07:11:48 | 2024-10-21 07:11:19 |
+----+--------+---------------------+---------------------+

4、代码示例

【1】gorm结构体定义

可以根据在线sql转go结构体来实现,转换之后如下:

package models

import (
	"time"
)

type TestPage struct {
	Id         int       `gorm:"column:id;type:int(11);AUTO_INCREMENT;primary_key" json:"id"`
	Number     int       `gorm:"column:number;type:int(11);NOT NULL" json:"number"`
	CreateTime time.Time `gorm:"column:create_time;type:datetime;default:CURjsRENT_TIMESTAMP" json:"create_time"`
	UpdateTime time.Time `gorm:"column:update_time;type:datetime;default:CURRENT_TIMESTAMP" json:"update_time"`
}

func (m *TestPage) TableName() string {
	return "test_page"
}

【2】分页结构体定义

package types

import "GoTest/gorm_demo/models"

type PageReq struct {
	Page     int `form:"page,default=1"`
	PageSize int `form:"page_size,default=4"`
}

type PageResult struct {
	TotalPage  int  `json:"total_page"`  //总页数
	TotalCount int  `json:"total_count"` //总条数
	Page       int  `json:"page"`        //当前页
	PageSize   int  `json:"page_size"`   //当前页数据量
	PrevPage   bool `json:"prev_page"`   //是否存在上页
	NextPage   bool `json:"next_page"`   //是否存在下页
}

//要测试的分页结构
type TestPageResp struct {
	PageResult
	List []models.TestPage `json:"list"`
}

【3】封装分页方法

package page

import (
	"math"

	"GoTest/gorm_demo/types"
)

// GetOffsetAndLimit
//
//	@Description: 获取查数据库的偏移量和限制数
//	@param page 当前页
//	@param pageSize 业数据量
//	@return offset 数据库偏移
//	@return limit 数据库限制量
func GetOffsetAndLimit(page, pageSize int) (offset, limit int) {
	limit = pageSize
	if limit <= 0 {
		limit = 4
	}

	if page > 1 {
		offset = (page - 1) * limit
	}

	return
}

// GetPageResult
//
//	@Description: 获取分页结果
//	@param pg 当前页
//	@param pageSize 页数据量
//	@param totalCount 根据此数量进行分页
//	@return types.PageResult 分页结构
func GetPageResult(pg, pageSize, totalCount int) types.PageResult {
	res := types.PageResult{}
	res.TotalCount = totalCount
	res.PageSize = pageSize
	res.Page = pg
	if pageSize > 0 && totalCount > 0 {
		res.TotalPage = int(math.Ceil(float64(totalCount) / float64(pageSize)))
	}

	if res.Page < res.TotalPage {
		res.NextPage = true
	}
	if res.Page > 1 {
		res.PrevPage = true
	}

	return res
}

【4】封装获取数据库连接方法

使用gorm连接mysql

package conn

import (
	"fmt"
	"runtime"
	"sync"
	"time"

	"GoTest/comm/logger"

	"go.uber.org/zap"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	gormLog "gorm.io/gorm/logger"
)

type MysqlDb string

const (
	Xxx MysqlDb = "xxx"
)

var (
	mysqlConn = map[MysqlDb]*gorm.DB{}
	mysqlLock = sync.RWMutex{}
)

func GetXxxDbConn() *gorm.DB {
	return getDbPool(Xxx)
}

func getDbPool(dbKey MysqlDb) *gorm.DB {
	mysqlLock.RLock()
	if conn, ok := mysqlConn[dbKey]; ok {
		mysqlLock.RUnlock()

		return conn
	}
	mysqlLock.RUnlock()

	mysqlLock.Lock()
	defer mysqlLock.Unlock()

	var dsn string
	switch dbKey {
	case Xxx:
		dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=5s", "xxx", "xxx", "127.0.0.1:3306", "xxx")
	default:
		logger.Error("unknown db", zap.String("db", string(dbKey)))
		return nil
	}

	conn := newConnect(dsn)
	mysqlConn[dbKey] = conn

	return conn
}

func newConnect(dsn string) *gorm.DB {
	conn, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: gormLog.Default.LogMode(gormLog.Info),
	})
	if err != nil {
		logger.Error("connect mysql error", zap.String("dsn", dsn), zap.Error(err))
		return nil
	}

	dbPool, err := conn.DB()
	if err != nil {
		logger.Error("get mysql db error", zap.String("dsn", dsn), zap.Error(err))
		return nil
	}

	logger.Info("conn mysql success")

	//连接池中中最大空闲时间
	dbPool.SetConnMaxIdleTime(time.Hour)
	//连接池中空闲连接的最大数量
	dbPool.SetMaxIdleConns(runtime.NumCPU() * 5)
	//数据库连接的最大数量
	dbPool.SetMaxOpenConns(runtime.NumCPU() * 10)
	//连接可复用最大时间
	dbPool.SetConnMaxLifetime(time.Hour * 24)

	return conn
}

使用时只需要调用GetXxxDbConn函数获取连接即可。

【5】查询列表接口

使用gChina编程in框架启动一个http服务,并注册一条路由用于查询列表接口:

r := gin.New()

	r.GET("/page/list", GetPageListHandler)

	if err := r.Run(":8888"); err != nil {
		panic(err)
	}

查询列表接口回调函数实现如下:

func GetPageListHandler(c *gin.Context) {
	var req types.PageReq

	if err := c.ShouldBind(&req); err != nil {
		logger.Error("invalid req", zap.Error(err))
		c.String(500, "invalid req")

		return
	}

	logger.Info("get page list req", zap.Any("req", req))

	db := conn.GetXxxDbConn()
	resp := types.TestPageResp{}
	resp.List = make([]models.TestPage, 0)

	//获取所有数量
	var count int64
	if err := db.Model(&models.TestPage{}).Count(&count).Error; err != nil {
		logger.Error("get count error", zap.Error(err))
		c.String(500, "get count error")
		return
	}

	//计算分页数据
	resp.PageResult = page.GetPageResult(req.Page, req.PageSize, int(count))

	//计算偏移量
	offset, limit := page.GetOffsetAndLimit(req.Page, req.PageSize)

	//数据库查询
	if err := db.Model(&models.TestPage{}).Order("create_time DESC").Offset(offset).Limit(limit).Find(&resp.List).Error; err != nil {
		logger.Error("get list error", zap.Error(err))
		c.String(500, "get list error")
		return
	}

	//返回数据
	c.JSON(http.StatusOK, resp)
}

【6】启动http服务

控制台输出如下:

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /page/list                --> main.GetPageListHandler (1 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8888

这里使用的是gin框架的debug模式,生产环境中不要用gin框架的debug模式,会影响程序的qps

【7】调用获取列表接口

使用curl命令来调用,获取第2页,页数据量为6条的数据,控制台输出如下:

[xxx@xxx ~]# curl -v -X GET --location "http://127.0.0.1:8888/page/list?page=2&page_size=6"
* About to connect() to 127.0.0.1 port 8888 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
> GET /page/list?page=2&page_size=6 HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:8888
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Mon, 21 Oct 2024 09:23:13 GMT
< Content-Length: 735
< 
* Connection #0 to host 127.0.0.1 left intact
{
    "total_page": 5,    //总共有5页
    "total_count": 30,  //根据30条数据进行分页
    "page": 2,          //当前页为第2页
    "page_size": 6,     //每一页的大小为6
    "prev_page": true,  //上一页存在
    "next_page": true,  //下一页存在
    "list": [           //根据创建时间倒序的列表
        {
            "id": 24,
            "number": 24,
            "create_time": "2024-10-21T07:11:42+08:00",
            "update_time": "2024-10-21T07:11:19+08:00"
        },
        {
            "id": 23,
            "number": 23,
            "create_time": "2024-10-21T07:11:41+08:00",
            "update_time": "2024-10-21T07:11:19+08:00"
javascript        },
        {
            "id": 22,
            "number": 22,
            "create_time": "2024-10-21T07:11:40+08:00",
            "update_time": "2024-10-21T07:11:19+08:00"
        },
        {
            "id": 21,
            "number": 21,
            "create_time": "2024-10-21T07:11:39+08:00",
            "update_time": "2024-10-21T07:11:19+08:00"
        },
        {
            "id": 20,
            "number": 20,
            "create_time": "2024-10-21T07:11:38+08:00",
            "update_time": "2024-10-21T07:11:19+08:00"
        },
        {
            "id": 19,
            "number": 19,
            "create_time": "2024-10-21T07:11:37+08:00",
            "update_time": "2024-10-21T07:11:19+08:00"
        }
    ]
}

此时gin程序控制台输出:

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /page/list                --> main.GetPageListHandler (1 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8888
[2024-10-21 17:23:13.435] | INFO  | Goroutine:34 | [gorm_demo/main.go:131]      | get page list req | {"req": {"Page":2,"PageSize":6}} //分页请求数据
[2024-10-21 17:23:13.437] | INFO  | Goroutine:34 | [conn/mysql.go:74]           | conn mysql success

2024/10/21 17:23:13 D:/GoTest/gorm_demo/main.go:139
[0.640ms] [rows:1] SELECT count(*) FROM `test_page`  //查询所有数量的sql

2024/10/21 17:23:13 D:/GoTest/gorm_demo/main.go:152
[0.325ms] [rows:6] SELECT * FROM `test_page` ORDER BY create_time DESC LIMIT 6 OFFSET 6 //根据分页请求计算出的偏移位置和限制量sql

上面打印了gorm库中的日志信息,可以通过自定义zap对象来实现gormlogger对象来自定义gorm输出日志。

总结

分页的方法有很多,上面只是给出了其中一种,根据自己项目的业务场景选择一种最喜欢的方法就行。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于Golang如何用gorm实现分页的功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155211

相关文章

Mybatis-Plus 3.5.12 分页拦截器消失的问题及快速解决方法

《Mybatis-Plus3.5.12分页拦截器消失的问题及快速解决方法》作为Java开发者,我们都爱用Mybatis-Plus简化CRUD操作,尤其是它的分页功能,几行代码就能搞定复杂的分页查询... 目录一、问题场景:分页拦截器突然 “失踪”二、问题根源:依赖拆分惹的祸三、解决办法:添加扩展依赖四、分页

MySQL容灾备份的实现方案

《MySQL容灾备份的实现方案》进行MySQL的容灾备份是确保数据安全和业务连续性的关键步骤,容灾备份可以分为本地备份和远程备份,主要包括逻辑备份和物理备份两种方式,下面就来具体介绍一下... 目录一、逻辑备份1. 使用mysqldump进行逻辑备份1.1 全库备份1.2 单库备份1.3 单表备份2. 恢复

Java对接MQTT协议的完整实现示例代码

《Java对接MQTT协议的完整实现示例代码》MQTT是一个基于客户端-服务器的消息发布/订阅传输协议,MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛,:本文主要介绍Ja... 目录前言前置依赖1. MQTT配置类代码解析1.1 MQTT客户端工厂1.2 MQTT消息订阅适配器1.

MySQL中处理数据的并发一致性的实现示例

《MySQL中处理数据的并发一致性的实现示例》在MySQL中处理数据的并发一致性是确保多个用户或应用程序同时访问和修改数据库时,不会导致数据冲突、数据丢失或数据不一致,MySQL通过事务和锁机制来管理... 目录一、事务(Transactions)1. 事务控制语句二、锁(Locks)1. 锁类型2. 锁粒

MyBatis流式查询两种实现方式

《MyBatis流式查询两种实现方式》本文详解MyBatis流式查询,通过ResultHandler和Cursor实现边读边处理,避免内存溢出,ResultHandler逐条回调,Cursor支持迭代... 目录MyBATis 流式查询详解:ResultHandler 与 Cursor1. 什么是流式查询?

Springboot项目登录校验功能实现

《Springboot项目登录校验功能实现》本文介绍了Web登录校验的重要性,对比了Cookie、Session和JWT三种会话技术,分析其优缺点,并讲解了过滤器与拦截器的统一拦截方案,推荐使用JWT... 目录引言一、登录校验的基本概念二、HTTP协议的无状态性三、会话跟android踪技术1. Cook

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

mybatis用拦截器实现字段加解密全过程

《mybatis用拦截器实现字段加解密全过程》本文通过自定义注解和MyBatis拦截器实现敏感信息加密,处理Parameter和ResultSet,确保数据库存储安全且查询结果解密可用... 目录前言拦截器的使用总结前言根据公司业务需要,灵活对客户敏感信息进行加解密,这里采用myBATis拦截器进行简单实

java实现多数据源切换方式

《java实现多数据源切换方式》本文介绍实现多数据源切换的四步方法:导入依赖、配置文件、启动类注解、使用@DS标记mapper和服务层,通过注解实现数据源动态切换,适用于实际开发中的多数据源场景... 目录一、导入依赖二、配置文件三、在启动类上配置四、在需要切换数据源的类上、方法上使用@DS注解结论一、导入

SQLServer中生成雪花ID(Snowflake ID)的实现方法

《SQLServer中生成雪花ID(SnowflakeID)的实现方法》:本文主要介绍在SQLServer中生成雪花ID(SnowflakeID)的实现方法,文中通过示例代码介绍的非常详细,... 目录前言认识雪花ID雪花ID的核心特点雪花ID的结构(64位)雪花ID的优势雪花ID的局限性雪花ID的应用场景