likes
comments
collection
share

gorm如何处理查询为空

作者站长头像
站长
· 阅读数 91

前言

gorm是go语言的一个orm框架

gorm对查询结果为空的处理主要体现在gorm.ErrRecordNotFound上,这是预定义的一个错误类型。字面上理解,当查询不到数据时框架会返回该错误,但实际情况另有玄机

本文将从以下三个方面进行介绍:

  • 所有查询方法都会返回该错误类型吗
  • 遇到这种错误应该怎么处理
  • 那为什么需要定义该类型

出现场景

gorm.ErrRecordNotFound并不是所有查询场景都会出现,需在特定的查询方法配合特定的查询条件,我们顺着源码依次看👀

gorm会默认注册的查询回调函数queryCallback

 // Define callbacks for querying

func init() {

   DefaultCallback.Query().Register("gorm:query", queryCallback)

   // ...

}

在该函数中最后有这么一段

if scope.db.RowsAffected == 0 && !isSlice {

   scope.Err(ErrRecordNotFound)

}

可以看到,只有当查询结果为空,且用于接收结果的变量不是slice类型(也就是单个struct),就是往scope设置该错误

从定义的注释中也能发现该逻辑

// ErrRecordNotFound record not found error, happens when haven't find any matched data when looking up with a struct
var ErrRecordNotFound = gorm.ErrRecordNotFound

那么有哪些查询方法会执行该回调方法呢?通过源码跟踪,可知只有以下四种查询方法

 // First find first record that match given conditions, order by primary key

func (s *DB) First(out interface{}, where ...interface{}) *DB {}



 // Last find last record that match given conditions, order by primary key

func (s *DB) Last(out interface{}, where ...interface{}) *DB {}



 // Find find records that match given conditions

func (s *DB) Find(out interface{}, where ...interface{}) *DB {}



 // Scan scan value to a struct

func (s *DB) Scan(dest interface{}) *DB {}

因此,关于什么场景会出现该错误的总结如下:

  • 使用First,Last,Find,Scan四种查询方法之一进行查询(在code.byted.org/gopkg/gorm v1.0.4版本下,其他版本可能有出入)

    • 其他查询方法一定不会出现,例如Pluck方法,当然Pluck方法限制了接收变量一定是slice类型
  • 查询结果为空,且用于接收的遍历不是slice

    • 注意上面四个方法并没有限制接收对象不能是slice,也就是说可使用slice作为接收变量调用First方法,虽然和First方法查第一个的本意不符
    • 和普遍想法不同的是,并不是说用First查询不到就一定会返回gorm.ErrRecordNotFound错误,用Find查不到就一定不会返回,而是根据接收变量类型进行综合处理

如何处理

当框架中返回了,就需要考虑怎么处理

首先是对不可能返回该类型错误的查询方法,不需要进行处理

例如下面对err的判断就是不必要的,因为条件不可能成立

var emps []*model.Employee

if err := LocalWriteDB(ctx).Where("user_id=?", userId).Find(&emps).Error; err == nil {

   return authList, nil

 // 不必要的判断!  

} else if err == gorm.ErrRecordNotFound {

   return emps, nil

} else {

   return nil, err

}

接下来需要考虑,数据找不到在业务上是预期,是预期内的正常情况还是预期外的异常情况,大部分情况下属于正常情况,上层需要感知的更多是连接异常等系统性错误

如果将数据不存在当错异常返回,可能会给用户带来困扰,例如用户需要的是列表为空的提示,但这里将其当成异常处理,用户会得到服务器异常的提示

因此可以简单处理将返回的结果和err都置为为nil,如下所示:

emp := &model.Employee{}

if err := LocalWriteDB(ctx).Where("user_id=?", userId).First(&emps).Error; err == nil {

   return authList, nil 

} else if err == gorm.ErrRecordNotFound {

   return nil, nil

} else {

   return nil, err

}

若上层需要感知该错误类型,若将gorm.ErrRecordNotFound原封不动返回,会导致上层除了依赖dal层外,还依赖外部gorm,依赖过多不利于后期维护。因此可在dal层新定义错误类型返回(例如dal.RecordNotFound),这样外层只依赖dal层,dal层屏蔽掉上层对外部的特定工具的依赖

设计思考

有其他语言的orm框架并没有定义类似的错误,例如mybatis,其查询单条记录的代码如下所示

public <T> T selectOne(String statement, Object parameter) {

    List<T> list = this.selectList(statement, parameter);

    if (list.size() == 1) {

        return list.get(0);

    } else if (list.size() > 1) {

        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());

    } else {

        return null;

    }

}

可以看到,若业务代码预期查询单条记录,且查询不到,直接返回null

gorm和mybatis不一样,因为gorm采用了将查询结果填充到用户指定的接收变量上的实现方式,类似C代码风格,将要修改的数据放在参数中(beego-orm也是这样实现)

若接收变量为slice类型,可通过判断len(slice) == 0来确认查到了记录,但如果接收变量为单个的struct,则用户无法判断(由于引用存在,不可能把用户侧对该对象的引用置为nil),因此通过将err置为特定的gorm.ErrRecordNotFound,交给用户判断处理

回到正题,gorm怎么处理返回结果为空:

  1. 查询方法为指定的方法之一,且接收变量不是slice,且数据库查询结果为空,用err = gorm.ErrRecordNotFound表示
  2. 接收变量为slice,且数据库查询结果为空,根据slice长度是否为0进行判断

总结

一直以来在使用gorm时任务是认为gorm.ErrRecordNotFound只和特定方法有关,但实际上和接收类型也有关系。本文从源码分析该错error出现的场景,介绍了怎么合理地处理该错误,以及设计上的思考

转载自:https://juejin.cn/post/6979915555939713054
评论
请登录