是否需要与Valuer接口一起实施Scanner接口

时间:2019-10-09 10:29:02

标签: go interface go-gorm

我正在按要求工作,其中我收到一个包含日期值作为字符串的JSON对象。我的任务是将Date对象存储在数据库中。

这种事情:

{"start_date": "2019-05-29", "end_date": "2019-08-30"}
{"start_date": "2019-05-29", "end_date": null}

我已经实现了自定义Date类型

type Date struct {
    time.Time
}

我已经实现了UnmarshalJSON接口

func (d *Date) UnmarshalJSON(b []byte) (err error) {
    if b[0] == '"' && b[len(b)-1] == '"' {
        b = b[1 : len(b)-1]
    }

    // take care of null..
    if len(b) == 0 || string(b) == "null" {
        d.Time = time.Time{}
        return
    }

    d.Time, err = time.Parse("2006-01-02", string(b))
    return
}

还实现了Valuer接口,以将Value返回给sql.Driver。

func (d Date) Value() (driver.Value, error) {
    // check if the date was not set..
    if d.Time.IsZero() {
        return nil, nil
    }
    return d.Time.Format("2006-01-02"), nil
}

但是由于某些原因,直到日期都实现了扫描仪界面, 像这样的东西:

func (d Date) Scan(b interface{}) (err error) {
    ...
    return
}

问题:

ORM适配器(GORM)不在数据库中存储记录。有任何线索吗?

列出的完整代码为here

进一步详细说明。.

如果我两次运行上面的代码2,我会看到不同的行为,这取决于是否存在Scan()函数。

1次: 数据库结果

repl_test=# select id, start_date from customer_brokers;
 id | start_date
----+------------
  1 | 2019-05-29

现在一切顺利..运行相同的代码..(注释了Scan()函数)

第二次。

repl_test=# select id, start_date from customer_brokers;
 id | start_date
----+------------
  1 | 2019-05-29
  2 |             <- start_date empty here..

2 个答案:

答案 0 :(得分:2)

启用DB.LogMode(true)会显示这两个查询

带扫描:

[2019-10-16 14:03:34]  [0.97ms]  INSERT  INTO "custom_brokers" ("created_at","updated_at","deleted_at","start_date","end_date") VALUES ('2019-10-16 14:03:34','2019-10-16 14:03:34',NULL,'2019-05-29',NULL) RETURNING "custom_brokers"."id"

不扫描:

[2019-10-16 14:02:53]  [0.76ms]  INSERT  INTO "custom_brokers" ("created_at","updated_at","deleted_at") VALUES ('2019-10-16 14:02:53','2019-10-16 14:02:53',NULL) RETURNING "custom_brokers"."id"

第二个结果显示gorm完全忽略了模型中的另一列(嵌入式gorm模型除外)

在调试之后,我尝试进行模型创建here,gorm检查该字段是否为sql.Scanner,然后设置IsNormal字段,该字段用于查询创建阶段。如果该字段不是sql.Scanner,则IsNormal为false,因此将被忽略here

因此,您的问题的答案是肯定的,即使您无需扫描它,也必须实现一种Scan方法。

答案 1 :(得分:1)

上一页:我跑了go get -u github.com/jinzhu/gorm。我将使用sqlite而不是pgsql。费,go version go1.12.7 linux/amd64

  1. 您的注释未使用正确的语法。 gorm-property-value-pair的分隔符不是 comma,而是semi-colonhttps://github.com/jinzhu/gorm/blob/master/model_struct.go#L644

  2. Date.Scan()的实现必须使用指针接收器,否则您将丢失更新后的值。 https://github.com/jinzhu/gorm/blob/0fd395ab37aefd2d50854f0556a4311dccc6f45a/scaner_test.go#L57

  3. 在测试期间,Scan方法收到了time.Time,因此不需要nil情况也不需要string情况(可能与数据库驱动程序有关) )。但是,更重要的是,我添加了一个default案例来查找不受支持的类型。

  4. 从数据库读取的数据分配给您提供的对象时,涉及到Scan方法。评论它与您在上一次sql结果复制粘贴中演示的缺少日期问题无关。我认为这与我之前提到的问题有关。

  5. 我听了所有错误以确保一切正常。

这是我的测试代码,

package main

import (
    "database/sql/driver"
    "encoding/json"
    "fmt"
    "log"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

var DB *gorm.DB

type CustomerBroker struct {
    gorm.Model
    StartDate Date `gorm:"type:date;column:start_date" json:"start_date"`
    EndDate   Date `gorm:"type:date;column:end_date" json:"end_date"`
}

type Date struct {
    time.Time
}

func (d *Date) UnmarshalJSON(b []byte) (err error) {
    if b[0] == '"' && b[len(b)-1] == '"' {
        b = b[1 : len(b)-1]
    }

    // take care of null..
    if len(b) == 0 || string(b) == "null" {
        d.Time = time.Time{}
        return
    }

    d.Time, err = time.Parse("2006-01-02", string(b))
    return
}

func (d *Date) Scan(b interface{}) (err error) {
    switch x := b.(type) {
    case time.Time:
        d.Time = x
  default:
    err = fmt.Errorf("unsupported scan type %T", b)
    }
    return
}

func (d Date) Value() (driver.Value, error) {
    // check if the date was not set..
    if d.Time.IsZero() {
        return nil, nil
    }
    return d.Time.Format("2006-01-02"), nil
}

func main() {
    var dberr error
    DB, dberr = gorm.Open("sqlite3", ":memory:")
    if dberr != nil {
        panic(dberr)
    }
    defer DB.Close()
    record := CustomerBroker{}
    errs := DB.CreateTable(record).GetErrors()
    fmt.Println("create error ", errs)
    data := []byte(`{"start_date": "2019-05-29", "end_date": null}`)
    err := json.Unmarshal(data, &record)
    fmt.Println("unmarshal error ", err)
    log.Printf("record start %v end %v\n", record.StartDate, record.EndDate)
    errs = DB.Create(&record).GetErrors()
    fmt.Println("insert error ", errs)
    all := []CustomerBroker{}
    errs = DB.Find(&all).GetErrors()
    fmt.Println("find error ", errs)
    log.Printf("records count %v\n", len(all))
    for _, a := range all {
        log.Printf("found start %v end %v\n", a.StartDate, a.EndDate)
    }
}

输出:

$ go run main.go 
create error  []
unmarshal error  <nil>
2019/10/11 17:22:51 record start 2019-05-29 00:00:00 +0000 UTC end 0001-01-01 00:00:00 +0000 UTC
insert error  []
find error  []
2019/10/11 17:22:51 records count 1
2019/10/11 17:22:51 found start 2019-05-29 00:00:00 +0000 UTC end 0001-01-01 00:00:00 +0000 UTC

如果针对pqsql运行此行为时发现其他行为,请共享相应的docker映像,以便将来可以针对同一版本进行尝试。