go项目中操作数据库使用gorm和gen对比,附查询案例代码

gorm和gen的区别

在上一篇文章《go-zero自动生成repository文件和测试用例》中,通过自定义的模板生成了go项目中常见的数据库操作方法,那么这里面其实存在一个细节问题,就是查询数据的时候应该使用gorm还是gen呢?

实际上,Gengorm里面的一个工具,或者说是在gorm的基础上做了更进一步的封装。GORMGORM Gen 都是 Go 语言中用于数据库操作的工具,它们在功能和使用方式上存在一些区别。

  • GORM 是一个强大且广泛使用的 Go 语言 ORM(对象关系映射)库,它为开发者提供了丰富的数据库操作功能,如创建、读取、更新和删除(CRUD)操作,支持多种数据库类型,像 MySQL、PostgreSQL 等。GORM 以链式调用的方式构建查询,具有较高的灵活性,开发者可以根据具体需求动态构建复杂的查询语句。然而,这种灵活性也带来了一定的学习成本,特别是在处理复杂查询时,代码可能会变得冗长且难以维护。
  • GORM Gen 则是基于 GORM 开发的代码生成工具,它通过生成预定义的查询方法,减少了手动编写数据库操作代码的工作量。GORM Gen 会根据数据库表结构自动生成对应的模型和查询方法,使得代码更加简洁、规范。开发者只需调用这些生成的方法,就可以完成常见的数据库操作,提高了开发效率。但 GORM Gen 的灵活性相对较低,对于一些特殊的查询需求,可能需要手动编写额外的代码。
  • 总的来说,GORM 适合需要高度灵活性和对数据库操作有深入控制的场景,而 GORM Gen 则更适合快速开发和代码规范统一的项目。

gorm官网:https://gorm.io/

gen的用法:https://gorm.io/gen/

使用案例

接下来,我通过一个常见的查询场景来分析应该使用哪种方式。

比如,有一个用户信息表的结构如下:

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `uuid` varchar(255) NOT NULL DEFAULT '' COMMENT 'uuid',
  `user_id` bigint(20) unsigned NOT NULL COMMENT '用户编号',
  `user_name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(255) NOT NULL DEFAULT '' COMMENT '密码',
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户姓名',
  `age` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '用户年龄',
  `email` varchar(255) NOT NULL DEFAULT '' COMMENT '用户邮箱',
  `address` varchar(255) NOT NULL DEFAULT '' COMMENT '地址',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1审核中,2正常,3禁用,4封号',
  `extra` text COMMENT '扩展数据',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除:0未删除,1已删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';

业务场景是,如要根据如下规则对用户信息的列表查询:

1、精确匹配(=): id、user_id、status、email;
2、模糊匹配(like): user_name、name、address;
3、大小匹配(>=和=<,可以兼容between and): age、create_time、update_time;
4、in/not in/ != 查询:id、user_id、user_name;
5、其它复杂查询

gozero/internal/svc/service_context.go中定义数据库相关的上下文:

package svc

import (
	"context"
	"go-demo-2025/gozero/internal/config"
	"go-demo-2025/gozero/internal/model/dao/query"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type ServiceContext struct {
	Config config.Config
	DB     *gorm.DB     //gorm的db对象
	Model  *query.Query //gorm的gen的query对象
}

func NewServiceContext(c config.Config) *ServiceContext {
	db, _ := gorm.Open(mysql.Open(c.Mysql.DataSource), &gorm.Config{
		Logger: newLogger,
		NamingStrategy: schema.NamingStrategy{
			TablePrefix:   "",   // 表名前缀
			SingularTable: true, // 使用单数表名,启用该选项,会区分 user 和 users 表为两个不同的数据表
		},
	})

	return &ServiceContext{
		Config: c,
		DB:     db,
		Model:  query.Use(db),
	}
}

查询方法对比

1、使用gorm查询

先从基础的gorm查询开始,不适用Gen的情况下。查询方法代码如下:

func (r *UserRepo) GetUserList1(queryEqualConditions map[string]interface{}, queryMoreConditions map[string]interface{}, page, pageSize int) (list []*model.User, count int64, err error) {
	m := r.svcCtx.DB.Model(&model.User{})
	querys := m.WithContext(r.ctx).Debug()

	//基础查询条件
	queryEqualConditions["is_deleted"] = 0
	querys = querys.Where(queryEqualConditions)

	//更多查询条件
	if len(queryMoreConditions) > 0 {
		if queryMoreConditions["ids"] != nil {
			querys = querys.Where("id in ?", queryMoreConditions["ids"].([]int64))
		}
		if queryMoreConditions["user_name"] != "" {
			querys = querys.Where("user_name like ?", "%"+queryMoreConditions["user_name"].(string)+"%")
		}
		if queryMoreConditions["name"] != "" {
			querys = querys.Where("name like?", "%"+queryMoreConditions["name"].(string)+"%")
		}
		if queryMoreConditions["address"] != "" {
			querys = querys.Where("address like?", "%"+queryMoreConditions["address"].(string)+"%")
		}
		if queryMoreConditions["age_min"] != 0 {
			querys = querys.Where("age >=?", queryMoreConditions["age_min"].(int64))
		}
		if queryMoreConditions["age_max"] != 0 {
			querys = querys.Where("age <=?", queryMoreConditions["age_max"].(int64))
		}
		if queryMoreConditions["create_time_start"] != "" {
			// 将字符串类型的时间转换为 time.Time 类型
			createTimeStart, err := time.Parse("2006-01-02 15:04:05", queryMoreConditions["create_time_start"].(string))
			if err == nil {
				querys = querys.Where("create_time >=?", createTimeStart)
			}
		}
		if queryMoreConditions["create_time_end"] != "" {
			// 将字符串类型的时间转换为 time.Time 类型
			createTimeEnd, err := time.Parse("2006-01-02 15:04:05", queryMoreConditions["create_time_end"].(string))
			if err == nil {
				querys = querys.Where("create_time <=?", createTimeEnd)
			}
		}
	}

	querys.Limit(pageSize).Offset(page).Find(&list)
	querys.Count(&count)
	return list, count, nil
}

测试用例代码:

func TestGetUserListDemo1(t *testing.T) {
	reqData := BuildRequestMockData()

	//构造等于的查询条件
	condsEq := make(map[string]interface{})
	if reqData.UserId != 0 {
		condsEq["user_id"] = reqData.UserId
	}
	if reqData.Status != 0 {
		condsEq["status"] = reqData.Status
	}
	if reqData.Email != "" {
		condsEq["email"] = reqData.Email
	}

	//构造其他查询条件
	condsMore := make(map[string]interface{})
	if len(reqData.Ids) > 0 {
		condsMore["ids"] = reqData.Ids //in
	}
	if reqData.UserName != "" { //like
		condsMore["user_name"] = reqData.UserName
	}
	if reqData.Name != "" { //like
		condsMore["name"] = reqData.Name
	}
	if reqData.Address != "" { //like
		condsMore["address"] = reqData.Address
	}
	if reqData.AgeMin != 0 { //>=
		condsMore["age_min"] = reqData.AgeMin
	}
	if reqData.AgeMax != 0 { //<=
		condsMore["age_max"] = reqData.AgeMax
	}
	if reqData.CreateTimeStart != "" { //>=
		condsMore["create_time_start"] = reqData.CreateTimeStart
	}
	if reqData.CreateTimeEnd != "" { //<=
		condsMore["create_time_end"] = reqData.CreateTimeEnd
	}

	//执行查询
	ts := utils.NewTestCtx()
	repo := mysql.NewUserRepo(ts.Ctx, ts.SvcCtx)
	list, count, err := repo.GetUserList1(condsEq, condsMore, 1, 20)
	t.Log("list: ", utils.EchoJson(list))
	t.Log("count: ", count)
	t.Log("error: ", err)
	assert.NoError(t, err)
}

运行结果:

image-20250411132656639

生成的SQL语句:

SELECT * FROM `user` WHERE (`email` = 'test@125.com' AND `is_deleted` = 0 AND `status` = 1) AND id in (1,2,3) AND user_name like '%zhangsan%' AND name like'%张三%' AND address like'%beijing%' AND age >=10 AND age <=50 AND create_time >='2024-01-01 08:00:00' AND create_time <='2024-01-02 07:59:59' LIMIT 20 OFFSET 1

上面的写法中,针对相等查询封装到了一个map中,针对非等查询,在repo中又一次逐一判断,有点繁琐,可以优化如下:

2、使用gorm查询优化

封装一个 ApplyQueryConditions 函数用于处理非等于查询条件:

// Condition 结构体用于存储操作符和值
type Condition struct {
	Field    string      // 字段名
	Operator string      // 操作符
	Value    interface{} // 值
}

// ApplyQueryConditions 函数用于处理非等于查询条件
func ApplyQueryConditions(query *gorm.DB, conditions []Condition) *gorm.DB {
	for _, cond := range conditions {
		switch cond.Operator {
		case "=":
			query = query.Where(fmt.Sprintf("%s = ?", cond.Field), cond.Value)
		case "!=":
			query = query.Where(fmt.Sprintf("%s !=?", cond.Field), cond.Value)
		case "like":
			query = query.Where(fmt.Sprintf("%s LIKE ?", cond.Field), "%"+cond.Value.(string)+"%")
		case "in":
			query = query.Where(fmt.Sprintf("%s IN ?", cond.Field), cond.Value)
		case ">":
			query = query.Where(fmt.Sprintf("%s > ?", cond.Field), cond.Value)
		case "<":
			query = query.Where(fmt.Sprintf("%s < ?", cond.Field), cond.Value)
		case ">=":
			query = query.Where(fmt.Sprintf("%s >= ?", cond.Field), cond.Value)
		case "<=":
			query = query.Where(fmt.Sprintf("%s <= ?", cond.Field), cond.Value)
		default:
			// 可以根据需要添加更多操作符
		}
	}
	return query
}

然后,查询方法优化如下:

func (r *UserRepo) GetUserList2(queryEqualConditions map[string]interface{}, queryMoreConditions []Condition, page, pageSize int) (list []*model.User, count int64, err error) {
	m := r.svcCtx.DB.Model(&model.User{})
	querys := m.WithContext(r.ctx).Debug()

	//基础查询条件
	queryEqualConditions["is_deleted"] = 0
	querys = querys.Where(queryEqualConditions)

	//更多查询条件
	if len(queryMoreConditions) > 0 {
		querys = ApplyQueryConditions(querys, queryMoreConditions)
	}

	querys.Limit(pageSize).Offset(page).Find(&list)
	querys.Count(&count)
	return list, count, nil
}

测试用例:

func TestGetUserListDemo2(t *testing.T) {
	reqData := BuildRequestMockData()

	//构造等于的查询条件
	condsEq := make(map[string]interface{})
	if reqData.UserId != 0 {
		condsEq["user_id"] = reqData.UserId
	}
	if reqData.Status != 0 {
		condsEq["status"] = reqData.Status
	}
	if reqData.Email != "" {
		condsEq["email"] = reqData.Email
	}

	//构造其他查询条件
	condsMore := make([]mysql.Condition, 0)
	if len(reqData.Ids) > 0 {
		condsMore = append(condsMore, mysql.Condition{"id", "in", reqData.Ids})
	}
	if reqData.UserName != "" {
		condsMore = append(condsMore, mysql.Condition{"user_name", "like", reqData.UserName})
	}
	if reqData.Name != "" {
		condsMore = append(condsMore, mysql.Condition{"name", "like", reqData.Name})
	}
	if reqData.Address != "" {
		condsMore = append(condsMore, mysql.Condition{"address", "like", reqData.Address})
	}
	if reqData.AgeMin != 0 {
		condsMore = append(condsMore, mysql.Condition{"age", ">=", reqData.AgeMin})
	}
	if reqData.AgeMax != 0 {
		condsMore = append(condsMore, mysql.Condition{"age", "<=", reqData.AgeMax})
	}
	if reqData.CreateTimeStart != "" {
		createTimeStart, err := time.Parse("2006-01-02 15:04:05", reqData.CreateTimeStart)
		if err == nil {
			condsMore = append(condsMore, mysql.Condition{"create_time", ">=", createTimeStart})
		}
	}
	if reqData.CreateTimeEnd != "" {
		createTimeEnd, err := time.Parse("2006-01-02 15:04:05", reqData.CreateTimeEnd)
		if err == nil {
			condsMore = append(condsMore, mysql.Condition{"create_time", "<=", createTimeEnd})
		}
	}

	//执行查询
	ts := utils.NewTestCtx()
	repo := mysql.NewUserRepo(ts.Ctx, ts.SvcCtx)
	list, count, err := repo.GetUserList2(condsEq, condsMore, 1, 20)
	t.Log("list: ", utils.EchoJson(list))
	t.Log("count: ", count)
	t.Log("error: ", err)
	assert.NoError(t, err)
}

运行结果和上面的是一样的:

SELECT * FROM `user` WHERE (`email` = 'test@125.com' AND `is_deleted` = 0 AND `status` = 1) AND id IN (1,2,3) AND user_name LIKE '%zhangsan%' AND name LIKE '%张三%' AND address LIKE '%beijing%' AND age >= 10 AND age <= 50 AND create_time >= '2024-01-01 08:00:00' AND create_time <= '2024-01-02 07:59:59' LIMIT 20 OFFSET 1

其实,这样封装后,也可以把等于的查询和其他查询放到一起了:

if reqData.UserId != 0 {
  condsMore = append(condsMore, mysql.Condition{"user_id", "=", reqData.UserId})
}
if reqData.Status != 0 {
  condsMore = append(condsMore, mysql.Condition{"status", "=", reqData.Status})
}
if reqData.Email != "" {
  condsMore = append(condsMore, mysql.Condition{"email", "=", reqData.Email})
}

以上写法是使用map的形式,优点是可以封装成统一的查询方法,缺点是如果写错了数据表的字段名,在编译阶段是不会报错的,只有运行的时候才会发现字段名不存在,报错Error 1054 (42S22): Unknown column 'idx' in 'where clause'。因此,可以改为使用结构体来查询。

3、使用gorm结构体查询

使用结构体查询的时候,只需要把之前定义的map改为对应的数据表的model的结构体即可。测试用例这样写:

func TestGetUserListByGormDemo3(t *testing.T) {
	reqData := BuildRequestMockData()

	//构造等于的查询条件
	condsEq := &model.User{} //User表的Model的结构体
	if reqData.Status != 0 {
		condsEq.Status = reqData.Status
	}
	if reqData.Email != "" {
		condsEq.Email = reqData.Email
	}

	//构造其他查询条件(暂未找到如何替换为结构体的方式)
	condsMore := make([]mysql.Condition, 0)
	if len(reqData.Ids) > 0 {
		condsMore = append(condsMore, mysql.Condition{"id", "in", reqData.Ids})
	}

	//执行查询
	ts := utils.NewTestCtx()
	repo := mysql.NewUserRepo(ts.Ctx, ts.SvcCtx)
	list, count, err := repo.GetUserListByGorm3(condsEq, condsMore, 1, 20)
}

通过上面的写法,可以确定condsEq.Status 一定是一个存在的字段。在查询方法中,传递的参数类型也改为queryEqualConditions *model.User

func (r *UserRepo) GetUserListByGorm3(queryEqualConditions *model.User, queryMoreConditions []Condition, page, pageSize int) (list []*model.User, count int64, err error) {
	m := r.svcCtx.DB.Model(&model.User{})
	querys := m.WithContext(r.ctx).Debug()

	//基础查询条件
	queryEqualConditions.IsDeleted = 1
	querys = querys.Where(queryEqualConditions)

	//更多查询条件
	if len(queryMoreConditions) > 0 {
		querys = ApplyQueryConditions(querys, queryMoreConditions)
	}

	querys.Limit(pageSize).Offset(page).Find(&list)
	querys.Count(&count)
	return list, count, nil
}

image-20250411144606219

SELECT * FROM `user` WHERE (`user`.`email` = 'test@125.com' AND `user`.`status` = 1 AND `user`.`is_deleted` = 1) AND 0 IN (1,2,3) LIMIT 20 OFFSET 1

但是,这样写会存在以下几个问题:

  • 对于非等于的查询,目前我还没找到怎么使用结构体赋值,但依然可以使用上面的map对于非等条件的赋值;
  • 如果给Model结构体的某个字段赋值为0或者空字符串,查询语句会忽略此字段,因为Model结构体的初始值对于int类型就是0,对于string类型就是空字符串。在map中不存在这个问题。 验证如下:我把上面的Status写死为0,把Email写死为空字符串:
//构造等于的查询条件
condsEq := &model.User{}
if reqData.Status != 0 {
  condsEq.Status = 0 //reqData.Status
}
if reqData.Email != "" {
  condsEq.Email = "" //reqData.Email
}

查询的语句就会忽略这两个字段:

image-20250411144958496

SELECT * FROM `user` WHERE `user`.`is_deleted` = 1 AND id IN (1,2,3) LIMIT 20 OFFSET 1

对于上面的问题,有个间接的办法就是:

  • 对于int类型,尽量避免使用0,对于状态“是”和“否”,不使用1和0,而使用1和2。
  • 对于string类型,可以自定义一个业务上用不到的字符串,例如“my_empty_str”。

以上,就是使用gorm查询的方法传值的差异和对比。关于gorm的更多查询方法参考:https://gorm.io/zh_CN/docs/query.html

接下来,使用Gen的方式来试试查询语句怎么写。

4、使用Gen查询

查询方法的代码如下:

func (r *UserRepo) GetUserListByGen1(queryEqualConditions map[string]interface{}, queryMoreConditions map[string]interface{}, page, pageSize int) (list []*model.User, count int64, err error) {
	m := r.svcCtx.Model.User
	querys := m.WithContext(r.ctx).Debug().Where(m.IsDeleted.Eq(0))

	//基础查询条件
	querys = querys.Where(field.Attrs(queryEqualConditions))

	//更多查询条件
	if len(queryMoreConditions) > 0 {
		if queryMoreConditions["ids"] != nil {
			querys = querys.Where(m.ID.In(queryMoreConditions["ids"].([]int64)...))
		}
		if queryMoreConditions["user_name"] != "" {
			querys = querys.Where(m.UserName.Like("%" + queryMoreConditions["user_name"].(string) + "%"))
		}
		if queryMoreConditions["age_min"] != 0 {
			querys = querys.Where(m.Age.Gte(queryMoreConditions["age_min"].(int64)))
		}
		if queryMoreConditions["age_max"] != 0 {
			querys = querys.Where(m.Age.Lte(queryMoreConditions["age_max"].(int64)))
		}
	}

	offset := (page - 1) * pageSize
	limit := pageSize
	res, count, err := querys.Order(m.ID.Desc()).FindByPage(offset, limit)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		logx.WithContext(r.ctx).Errorf("Failed to get list: %v", err)
		return nil, 0, err
	}
	return res, count, nil
}

测试用例的代码写法:

func TestGetUserListByGenDemo1(t *testing.T) {
	reqData := BuildRequestMockData()

	//构造等于的查询条件
	condsEq := make(map[string]interface{})
	if reqData.UserId != 0 {
		condsEq["user_id"] = reqData.UserId
	}
	if reqData.Status != 0 {
		condsEq["status"] = reqData.Status
	}
	if reqData.Email != "" {
		condsEq["email"] = reqData.Email
	}

	//构造其他查询条件
	condsMore := make(map[string]interface{})
	if len(reqData.Ids) > 0 {
		condsMore["ids"] = reqData.Ids //in
	}
	if reqData.UserName != "" { //like
		condsMore["user_name"] = reqData.UserName
	}
	if reqData.AgeMin != 0 { //>=
		condsMore["age_min"] = reqData.AgeMin
	}
	if reqData.AgeMax != 0 { //<=
		condsMore["age_max"] = reqData.AgeMax
	}

	//执行查询
	ts := utils.NewTestCtx()
	repo := mysql.NewUserRepo(ts.Ctx, ts.SvcCtx)
	list, count, err := repo.GetUserListByGen1(condsEq, condsMore, 1, 20)
	t.Log("list: ", utils.EchoJson(list))
	t.Log("count: ", count)
	t.Log("error: ", err)
	assert.NoError(t, err)
}

执行后的语句和上面没有区别:

image-20250414141345007

SELECT * FROM `user` WHERE `user`.`is_deleted` = 0 AND (`email` = 'test@125.com' AND `status` = 1) AND `user`.`id` IN (1,2,3) AND `user`.`user_name` LIKE '%zhangsan%' AND `user`.`age` >= 10 AND `user`.`age` <= 50 ORDER BY `user`.`id` DESC LIMIT 20

和上面使用gorm查询,最大的区别:

  • m := r.svcCtx.DB.Model(&model.User{}) 换成了 m := r.svcCtx.Model.User
  • querys = querys.Where(queryEqualConditions) 换成了 querys = querys.Where(field.Attrs(queryEqualConditions))
  • 这里的 querys.Order(m.ID.Desc()).FindByPage(offset, limit) 方法直接返回的就是三个参数:res, count, err

你可以查看Gen生成的源代码了解其更多方法:

image-20250414141622251

上面使用Gen的查询条件依然是封装了map,我仍然希望能够通过结构体属性来访问查询条件,这样如果我的字段名写错了,就可以在编译阶段报错。因此,继续优化为下面的写法。

5、使用Gen查询优化

由于在Gen的查询方法中,对于非等查询不太好封装成统一的方法,因此,我决定在repo层直接接收调用方(比如:logic层或测试用例处)传递来的req变量,然后针对每一类查询条件逐一处理。这样看起来代码也比较工整。

先从go-zero的api文件开始,定义需要查询用户列表的条件的结构体:

type UserListRequest {
	UserId   int64  `json:"user_id,optional"` // 用户id,eq查询
	Status   int64  `json:"status,optional"` // 用户状态,eq查询
	Email    string `json:"email,optional"` // 邮箱,eq查询
	Ids      string `json:"ids,optional"` // 多个id用逗号分隔,in查询
	UserName string `json:"user_name,optional"` // 用户名,like查询
	AgeMin   int64  `json:"age_min,optional"` // 年龄,>=
	AgeMax   int64  `json:"age_max,optional"` // 年龄,<=
	Page     int64  `json:"page,optional,default=1"`
	PageSize int64  `json:"page_size,optional,default=10"`
}

然后,在 gozero/internal/logic/admin/user_list_logic.go 中,把请求参数 req *types.UserListRequest 直接传递到repo层:

func (l *UserListLogic) UserList(req *types.UserListRequest) (resp []*types.UserListResponse, err error) {
	//根据前端传来的参数req查询用户数据列表
	userModelList, count, err := l.userRepo.GetUserListByGen2(req)
	if err != nil {
		return nil, err
	}

	//定义返回的结构体
	userListResp := make([]*types.UserListResponse, 0)
	//如果查询到的结果为空,直接返回预定义的空数据
	if count == 0 {
		return userListResp, nil
	}

	//二次处理userModelList,转换为前端需要的结构体
	// todo...
	fmt.Println(userModelList)

	return userListResp, nil
}

在repo层,需要根据前端传来的req请求参数,封装出一个公用的查询条件,这样可以给其他查询列表和总数之类的方法复用。代码路径:gozero/internal/repo/mysql/user_repo.go

// 构建查询用户数据的条件
func (r *UserRepo) BuildQueryUserListCondition(req *types.UserListRequest) (query.IUserDo, error) {
	m := r.svcCtx.Model.User
	querys := m.WithContext(r.ctx).Debug().Where(m.IsDeleted.Eq(0))
	if req.UserId > 0 {
		querys = querys.Where(m.ID.Eq(req.UserId)) //eq查询
	}
	if req.Status > 0 {
		querys = querys.Where(m.Status.Eq(req.Status)) //eq查询
	}
	if req.Email != "" {
		querys = querys.Where(m.Email.Eq(req.Email)) //eq查询
	}
	if req.Ids != "" {
		idsSlice := utils.CommaSeparatedStringToInt64Slice(req.Ids)
		if len(idsSlice) > 0 {
			querys = querys.Where(m.ID.In(idsSlice...)) //in查询
		}
	}
	if req.UserName != "" {
		querys = querys.Where(m.UserName.Like("%" + req.UserName + "%")) //like查询
	}
	if req.AgeMin > 0 {
		querys = querys.Where(m.Age.Gte(req.AgeMin)) //>=查询
	}
	if req.AgeMax > 0 {
		querys = querys.Where(m.Age.Lte(req.AgeMax)) //<=查询
	}

	return querys, nil
}

然后,在查询方法里面调用上面的查询条件,以及后续的查询逻辑:

func (r *UserRepo) GetUserListByGen2(req *types.UserListRequest) (list []*model.User, count int64, err error) {
	m := r.svcCtx.Model.User
	querys, err := r.BuildQueryUserListCondition(req)
	if err != nil {
		return nil, 0, err
	}

	limit := int(req.PageSize)
	offset := (int(req.Page) - 1) * limit
	res, count, err := querys.Order(m.ID.Desc()).FindByPage(offset, limit)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		logx.WithContext(r.ctx).Errorf("Failed to get list: %v", err)
		return nil, 0, err
	}
	return res, count, nil
}

接下来,启动服务,用一个全都满足的条件查询试一下:

image-20250414152301005

打印的SQL语句如下:

SELECT * FROM `user` WHERE `user`.`is_deleted` = 0 AND `user`.`user_id` = 1001004 AND `user`.`status` = 2 AND `user`.`email` = 'nieqingchun@test.com' AND `user`.`id` IN (3,4,5) AND `user`.`user_name` LIKE '%nie%' AND `user`.`age` >= 15 AND `user`.`age` <= 30 ORDER BY `user`.`id` DESC LIMIT 10

在测试用例中的测试效果:

func TestGetUserListByGenDemo2(t *testing.T) {
	reqData := &types.UserListRequest{
		UserId:   1001004,
		Status:   2,
		Email:    "nieqingchun@test.com",
		Ids:      "3,4,5",
		UserName: "nie",
		AgeMin:   15,
		AgeMax:   30,
		Page:     1,
		PageSize: 10,
	}

	//执行查询
	ts := utils.NewTestCtx()
	repo := mysql.NewUserRepo(ts.Ctx, ts.SvcCtx)
	list, count, err := repo.GetUserListByGen2(reqData)
	t.Log("list: ", utils.EchoJson(list))
	t.Log("count: ", count)
	t.Log("error: ", err)
	assert.NoError(t, err)
}

image-20250414152916426

综上对比,建议使用这样的方式,因为BuildQueryUserListCondition() 这里封装的查询条件可以给其他方法复用,而且,所有的查询条件的字段都是通过结构体的形式处理的,避免了map中字段写错编译也不报错的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农兴哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值