细数在用golang&beego做api server过程中的坑(二)

在介绍之前先说明一下,标题中带有【beego】标签的,是beego框架使用中遇到的坑。如果没有,那就是golang本身的坑。当然,此坑并非人家代码有问题,有几个地方反而是出于性能等各方面的考量有意而为之。但这些地方却是一门语言或框架的初学者大概率会遇到的困惑。
传送门:细数在用golang&beego做api server过程中的坑(一)

【beego】4、orm查询时没有对应结果集: now row found (error)

java程序员对于持久层框架可能都用过mybatis,对查询操作也会比较熟悉。当一行代码下去,数据库给你咔咔一顿找,发现没有结果时,会返回null 或 一个空的collection。beego中也有持久层框架,就叫orm。对于查询方面的处理,如果数据库查询后没有找到任何结果,orm不会返回空,而是会返回一个error!!
举例:

var container po.OnlineAccumulateRecord
    err = o.QueryTable(new(po.OnlineAccumulateRecord)).Filter("User", user.Id).One(&container)
    if err != nil && !models.IsNoRowFoundError(err) {
        return 0, err
    }

这段代码是我从现有工程里面随便摘出来的,大意就是通过用户id筛选出积分表里的一条记录。这个时候如果查不到对应的记录,err != nil 便会成立。这时候单纯的通过 err !=nil就无法判断到底是真的出现数据库查询错误,还是没有找到对应记录。因此就有了models.IsNoRowFoundError(err)这一判断。 对于是否是NoRowFoundError,可行的方法之一是判断err是否相等:

if err == orm.ErrNoRows {
}

之二是判断error message:

//判断一个错误是不是因为数据库中没有符合条件的记录,而抛出的错误。
//这种情况是一种正常的情况,不应该作为错误抛出
func IsNoRowFoundError(err error) bool {
    if err == nil {
        return false
    }

    if strings.Contains(err.Error(), "no row found") {
        return true
    }
    return false
}

当然对于其他的查询方式,也会有这个问题,例如:

o := orm.NewOrm()
err = o.Read(&orderInfo, "OuterOrderId")

在此就不一一列举。
我个人认为astaxie大神在设计beego orm时不可能没考虑到这一点。之所以这样设计,可能的一个原因是,对于刚才的查询代码:

var container po.OnlineAccumulateRecord
 err = o.QueryTable(new(po.OnlineAccumulateRecord)).Filter("User", user.Id).One(&container)

不能够通过判断container是不是nil来断定有没有查询到对应的记录。因为在golang中,一个变量由两部分组成:type & value,只要任何一部分是明确的,则该变量就不是nil。在上例中,container的type已经明确声明是po.OnlineAccumulateRecord, 此时value是零值。因此它永远都不会是nil。

【beego】5、orm插入记录,如果pk是非整型(e.g. string):no LastInsertId available error

orm设计时对于整型pk做了特别的支持:

当 Field 类型为 int, int32, int64, uint, uint32, uint64 时,可以设置字段为自增健
当模型定义里没有主键时,符合上述类型且名称为 Id 的 Field 将被视为自增健。

但如果主键是string,通过以下代码做insert操作时会返回"no LastInsertId available" error。因此需要增加IsNoLastInsertIdError判断以免影响程序运行的分支走向。

    o := orm.NewOrm()
    _, err = o.Insert(wechatNotify)
    if models.IsNoLastInsertIdError(err) {
        return nil
    }

同样的,IsNoLastInsertIdError判断的方法之一是根据error message判断:

//这种情况是一种正常的情况,不应该作为错误抛出
func IsNoLastInsertIdError(err error) bool {
    if err == nil {
        return false
    }

    if strings.Contains(err.Error(), "no LastInsertId available") {
        return true
    }

    return false
}

【beego】6、报错误导:<Ormer> table: '.' not found,

好不容易把代码撸完了,兴冲冲的运行debug,结果发现如下报错信息:

<Ormer> table: `.` not found, make sure it was registered with `RegisterModel()`

根据字面意思是说正在操作的这个名字为“.”的table没有进行regist操作。于是排查,但很快发现:
1、根本就没有一个叫"."的table
2、正在操作的表也已经在beego启动的时候完成了regist操作
然后通过源码进行定位,报错位置:


func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect.Value) {
    val := reflect.ValueOf(md)
    ind = reflect.Indirect(val)
    typ := ind.Type()
    if needPtr && val.Kind() != reflect.Ptr {
        panic(fmt.Errorf("<Ormer> cannot use non-ptr model struct `%s`", getFullName(typ)))
    }
    name := getFullName(typ)
    if mi, ok := modelCache.getByFullName(name); ok {
        return mi, ind
    }
    panic(fmt.Errorf("<Ormer> table: `%s` not found, make sure it was registered with `RegisterModel()`", name))
}

那这个name是怎么得出来的呢?

// get reflect.Type name with package path.
func getFullName(typ reflect.Type) string {
    return typ.PkgPath() + "." + typ.Name()
}

也就是说,type.PkgPath() 和 type.Name()都是empty string 返回值。那什么时候他们返回的是empty string 呢?

    // Name returns the type's name within its package for a defined type.
    // For other (non-defined) types it returns the empty string.
    Name() string

    // PkgPath returns a defined type's package path, that is, the import path
    // that uniquely identifies the package, such as "encoding/base64".
    // If the type was predeclared (string, error) or not defined (*T, struct{},
    // []int, or A where A is an alias for a non-defined type), the package path
    // will be the empty string.
    PkgPath() string

从上述代码可以看出,当o.Insert()接收到参数是not defined的话,就会导致该项错误,而并不是说这个玩意没有regist(就这些玩意咋去regist嘛)。 注释部分也说了not defined包括哪几种:*T, struct{}, []int,以及这几类的别名。
最后举两个能出现这个问题的例子:

//example 1
_struct := new(structExample)
o := orm.NewOrm()
o.Insert(&_pointer)

这里要特别说明golang里面new的用法:

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

注释很明确的提到,new返回的是一个指针。更浅显的犯错方式就像example2这样:

//example 2
var _struct structExample
var _pointer &_struct
o := orm.NewOrm()
o.Insert(&_pointer)

【beego】7、报错误导:panic: reflect: call of reflect.Value.Interface on zero Value

再一次好不容易把代码撸完了,兴冲冲的运行debug,结果发现如下报错信息:

panic: reflect: call of reflect.Value.Interface on zero Value
goroutine 1 [running]:
reflect.valueInterface(0x0, 0x0, 0x0, 0x1, 0x0, 0x0)
    /usr/local/Cellar/go/1.11/libexec/src/reflect/value.go:953 +0x2ce
reflect.Value.Interface(0x0, 0x0, 0x0, 0x0, 0x0)
    /usr/local/Cellar/go/1.11/libexec/src/reflect/value.go:948 +0x4c
github.com/astaxie/beego/orm.getFieldType(0x1af8900, 0xc0001e19e0, 0x196, 0x0, 0x0, 0x0)
    /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_utils.go:181 +0xd00
github.com/astaxie/beego/orm.newFieldInfo(0xc0001f9040, 0x1af8900, 0xc0001e19e0, 0x196, 0x1a873ee, 0x4, 0x0, 0x0, 0x1e2bba0, 0x1af8900, ...)
    /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_info_f.go:244 +0x372e
github.com/astaxie/beego/orm.addModelFields(0xc0001f9040, 0x1bf8820, 0xc0001e1980, 0x199, 0x0, 0x0, 0xc0000dd828, 0x0, 0x0)
    /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_info_m.go:70 +0x3f4
github.com/astaxie/beego/orm.newModelInfo(0x1af8340, 0xc0001e1980, 0x16, 0xc0001f9040)
    /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_info_m.go:45 +0x302
github.com/astaxie/beego/orm.registerModel(0x1c4b231, 0x4, 0x1af8340, 0xc0001e1980, 0x201)
    /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_boot.go:62 +0x7b0
github.com/astaxie/beego/orm.RegisterModelWithPrefix(0x1c4b231, 0x4, 0xc0000ddd38, 0x7, 0x7)
    /Users/tangxqa/develop/code/go/src/github.com/astaxie/beego/orm/models_boot.go:320 +0x16d
rrs.com/rrsservice/models/po.init.28()
    /Users/tangxqa/develop/code/go/src/rrs.com/rrsservice/models/po/payment_wx.go:147 +0x2aa

可以通过堆栈信息看出这一报错发生在regist model时,regist部分的代码为:

    orm.RegisterModelWithPrefix(prefix, new(PaymentInfo))

PaymentInfo:


type PaymentInfo struct {
    Id              string                `orm:"size(32);pk;column(id)"`
    Status          string                
    OrderId         string                
    OrderSource     string               
    PayType         string               
    OrderPrice      int                   
    UnifiedOrderRes *UnifiedOrderResponse `orm:"rel(fk);null"` 
    User            *User                       
    Ts              time.Time            
}

好像代码写的没毛病。只好再根据堆栈信息找到beego源码中:github.com/astaxie/beego/orm.getFieldType 位于 astaxie/beego/orm/models_utils.go:181getFieldType方法中是一堆类型判断代码:

// return field type as type constant from reflect.Value
func getFieldType(val reflect.Value) (ft int, err error) {
    switch val.Type() {
    case reflect.TypeOf(new(int8)):
        ft = TypeBitField
    case reflect.TypeOf(new(int16)):
        ft = TypeSmallIntegerField
    case reflect.TypeOf(new(int32)),
        reflect.TypeOf(new(int)):
        ft = TypeIntegerField
    case reflect.TypeOf(new(int64)):
        ft = TypeBigIntegerField
···
···

什么时候会走到这?那就看看github.com/astaxie/beego/orm.newFieldInfo 位于 gthub.com/astaxie/beego/orm/models_info_f.go:244的代码:
这些代码都是一些对 字段tag 的分析处理。等等,是不是忘记添加tag了!!

tag = "rel"
        tagValue = tags[tag]
        if tagValue != "" {
            switch tagValue {
            case "fk":
                fieldType = RelForeignKey
                break checkType
            case "one":
                fieldType = RelOneToOne
                break checkType
            case "m2m":
                fieldType = RelManyToMany
                if tv := tags["rel_table"]; tv != "" {
                    fi.relTable = tv
                } else if tv := tags["rel_through"]; tv != "" {
                    fi.relThrough = tv
                }
                break checkType
            default:
                err = fmt.Errorf("rel only allow these value: fk, one, m2m")
                goto wrongTag
            }
        }
        tag = "reverse"
        tagValue = tags[tag]
        if tagValue != "" {
            switch tagValue {
            case "one":
                fieldType = RelReverseOne
                break checkType
            case "many":
                fieldType = RelReverseMany
                if tv := tags["rel_table"]; tv != "" {
                    fi.relTable = tv
                } else if tv := tags["rel_through"]; tv != "" {
                    fi.relThrough = tv
                }
                break checkType
            default:
                err = fmt.Errorf("reverse only allow these value: one, many")
                goto wrongTag
            }
        }

        fieldType, err = getFieldType(addrField)

添加tag,搞定!!

   User            *User                `orm:"rel(fk)"`       

那是不是所有的字段在orm时都需要添加标签? 不是。当字段是指针类型时,如果没有用orm:"-"进行orm忽略,必须要添加标签来进行表关系设置。
相关资料:https://beego.me/docs/mvc/model/models.md 参照其中的表关系设置章节。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容