7

可能这就是做开源项目的意义吧!

 1 year ago
source link: https://www.51cto.com/article/749961.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

可能这就是做开源项目的意义吧!

作者:王中阳Go 2023-03-23 11:56:01
使用了with模型关联,核心思路是:根据传入参数区分查询对应的关联模型,不做无效查询。
  1. 使用goframe v2最新版的最佳实践
  2. 列表取值slice容量初始化,避免scan动态扩容
  3. slice的延迟初始化
  4. 更新操作注意的问题

老规则:我把详细步骤已经整理好,大家可以参考这个步骤进行开发,更欢迎提优化建议。

取值列表优化

下方代码示例是项目之前的列表取值写法,和官方示例focus-single写法一样,思路如下:

  1. 获得*gdb.Model对象,方便后续调用
  2. 实例化返回结构体
  3. 执行查询和赋值(只是为了查询有无数据,并没有赋值到响应结构体中)
  4. 无数据判断
  5. 再查询count,获得数据个数
  6. 把查询到的结果赋值到响应结构体中

每段代码都写清楚了注释,这么写能实现功能,但是性能不够好,还有优化空间:

// GetList 查询内容列表
func (s *sAdmin) GetList(ctx context.Context, in model.AdminGetListInput) (out *model.AdminGetListOutput, err error) {
 //1.获得*gdb.Model对象,方便后续调用
 var (
  m = dao.AdminInfo.Ctx(ctx)
 )
 //2. 实例化返回结构体
 out = &model.AdminGetListOutput{
  Page: in.Page,
  Size: in.Size,
 }
 //3. 分页查询
 listModel := m.Page(in.Page, in.Size)
 //4. 执行查询和赋值(只是为了查询有无数据,并没有赋值到响应结构体中)
 var list []*entity.AdminInfo
 if err := listModel.Scan(&list); err != nil {
  return out, err
 }
 //5.无数据判断
 if len(list) == 0 {
  return out, nil
 }
 //6. 再查询count,获得数据个数
 out.Total, err = m.Count()
 if err != nil {
  return out, err
 }
 //7. 把查询到的结果赋值到响应结构体中
 if err := listModel.Scan(&out.List); err != nil {
  return out, err
 }
 return
}

整理一下上面代码的问题:

  1. 步骤4没有必要,可以直接查询count,如果count为0直接返回;否则再执行查询赋值操作
  2. 上述这种写法有个问题:当没有查询到数据时,list值为null,但是我们期望的返回值为空数组[]
图片
  1. 还有就是slice的容量初始化下会更好,scan期间不会有扩容行为
  2. 再者就是延迟slice的初始化,如果前面出错,就没有必要实例化列表了

我们优化一下代码,优化后的代码如下,也写了详细的注释:

  1. 获得*gdb.Model对象,方便后续调用
  2. 实例化响应结构体
  3. 再查询count,判断有无数据
  4. 延迟初始化list切片 确定有数据,再按期望大小初始化切片容量
  5. 把查询到的结果赋值到响应结构体中
// GetList 查询内容列表
func (s *sAdmin) GetList(ctx context.Context, in model.AdminGetListInput) (out *model.AdminGetListOutput, err error) {
 //1. 获得*gdb.Model对象,方便后续调用
 m := dao.AdminInfo.Ctx(ctx)
 //2. 实例化响应结构体
 out = &model.AdminGetListOutput{
  Page: in.Page,
  Size: in.Size,
 }
 //3. 分页查询
 listModel := m.Page(in.Page, in.Size)
 //4. 再查询count,判断有无数据
 out.Total, err = m.Count()
 if err != nil || out.Total == 0 {
   //解决空数据返回[] 而不是返回nil的问题
  out.List = make([]model.AdminGetListOutputItem, 0, 0)
  return out, err
 }
 //5. 延迟初始化list切片 确定有数据,再按期望大小初始化切片容量
 out.List = make([]model.AdminGetListOutputItem, 0, in.Size)
 //6. 把查询到的结果赋值到响应结构体中
 if err := listModel.Scan(&out.List); err != nil {
  return out, err
 }
 return
}

优化代码之后,无数据的list返回格式和预期一样为[]:

图片

这是有数据的返回结果示例:

图片

以上优化记录已经同步到GitHub,欢迎查看、复刻经验:

​https://github.com/wangzhongyang007/goframe-shop-v2/commit/ee020ea96616c30cb5bce5f7ab24417ad56e1a67​

上面的例子很简单,就是普通的查询数据,也没有搜索条件,也不涉及到模型关联。

关联查询取值

咱们再举一个复杂点的例子,带大家进阶一下,我就直接安排优化后的代码了:

  1. 定义全局通用的查询语句
  2. 实例化响应结构体
  3. 优先查询count,报错或者无数据则直接返回
  4. 延迟初始化list 确定有数据再按期望大小,实例化切片的容量
  5. 进一步优化:根据传入参数区分查询对应的关联模型
// GetList 查询内容列表
func (*sCollection) GetList(ctx context.Context, in model.CollectionListInput) (out *model.CollectionListOutput, err error) {
 //1. 定义全局通用的查询语句
 userId := gconv.Uint(ctx.Value(consts.CtxUserId))
 m := dao.CollectionInfo.Ctx(ctx).Where(dao.CollectionInfo.Columns().Type, in.Type).
  Where(dao.CollectionInfo.Columns().UserId, userId)
 //2. 实例化响应结构体
 out = &model.CollectionListOutput{
  Page: in.Page,
  Size: in.Size,
 }
 //3. 翻页查询
 listModel := m.Page(in.Page, in.Size)
 //4. 优先查询count,报错或者无数据则直接返回
 out.Total, err = listModel.Count()
 if err != nil || out.Total == 0 {
  out.List = make([]model.CollectionListOutputItem, 0, 0)
  return out, err
 }
 //5. 延迟初始化list 确定有数据再按期望大小,实例化切片的容量
 out.List = make([]model.CollectionListOutputItem, 0, in.Size)
 //6. 进一步优化:根据传入参数区分查询对应的关联模型
 if in.Type == consts.CollectionTypeGoods {
  if err := listModel.With(model.GoodsItem{}).Scan(&out.List); err != nil {
   return out, err
  }
 } else if in.Type == consts.CollectionTypeArticle {
  if err := listModel.With(model.ArticleItem{}).Scan(&out.List); err != nil {
   return out, err
  }
 } else {
  if err := listModel.WithAll().Scan(&out.List); err != nil {
   return out, err
  }
 }
 return
}

上面的示例使用了with模型关联,核心思路是:根据传入参数区分查询对应的关联模型,不做无效查询。

使用OmitEmpty,更新操作过滤空值,比如:

func (*sAddress) Update(ctx context.Context, in model.UpdateAddressInput) (err error) {
 if _, err = dao.AddressInfo.Ctx(ctx).Data(in).OmitEmpty().Where(dao.AddressInfo.Columns().Id, in.Id).Update(); err != nil {
  return err
 }
 return nil
}

我开发过程中原本没有使用OmitEmpty(),忽略了这个问题,感谢这位朋友提的建议。

开源项目地址:

做开源项目这件事,从没想过一蹴而就,想得一直是越来越好,投入长期精力:​https://github.com/wangzhongyang007/goframe-shop-v2​

本文转载自微信公众号「 程序员升级打怪之旅」,作者「王中阳Go」,可以通过以下二维码关注。

856d4c3842966d091a99323f2d1a93af84ad56.jpg

转载本文请联系「 程序员升级打怪之旅」公众号。

责任编辑:武晓燕 来源: 程序员升职加薪之旅

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK