是否需要与 Valuer 接口一起实现 Scanner 接口
is it necessay to implement Scanner interface along with Valuer interface
我正在处理一个要求,其中我收到一个 JSON 对象,其中包含一个字符串形式的日期值。我的任务是将日期对象存储在数据库中。
这类东西:
{"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 接口 return 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
}
但由于某些原因,直到 Date 实现了 Scanner 接口,
像这样:
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..
前:我运行go get -u github.com/jinzhu/gorm
。我将使用 sqlite
而不是 pgsql
。仅供参考,go version go1.12.7 linux/amd64
您的注释没有使用正确的语法。 gorm-属性-value 对的分隔符是 而不是 comma
,而是 semi-colon
。 https://github.com/jinzhu/gorm/blob/master/model_struct.go#L644
Date.Scan()
的实现必须使用指针接收器,否则会丢失更新后的值。 https://github.com/jinzhu/gorm/blob/0fd395ab37aefd2d50854f0556a4311dccc6f45a/scaner_test.go#L57
在我的测试中,Scan
方法收到 time.Time
,因此不需要 nil
案例,也不需要 string
案例(可能是数据库驱动程序的问题)。但是,更重要的是,我添加了一个 default
案例来捕获不受支持的类型。
Scan
方法涉及将从数据库读取的数据分配给您提供的对象。评论它与您在上次 sql 结果复制粘贴中演示的缺失日期问题无关。我认为这在某种程度上与我之前提到的问题有关。
我听取了所有错误以确保一切正常。
这是我的测试代码,
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 图像,以便将来可以对同一版本进行尝试。
启用 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 checks if the field is sql.Scanner
and then set the IsNormal
field, which is used in query creation phase. if the field is not a sql.Scanner
, the IsNormal
is false so it's ignored here
所以,你的问题的答案是肯定的,即使你不需要扫描它也必须实现一个Scan
方法。
我正在处理一个要求,其中我收到一个 JSON 对象,其中包含一个字符串形式的日期值。我的任务是将日期对象存储在数据库中。
这类东西:
{"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 接口 return 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
}
但由于某些原因,直到 Date 实现了 Scanner 接口, 像这样:
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..
前:我运行go get -u github.com/jinzhu/gorm
。我将使用 sqlite
而不是 pgsql
。仅供参考,go version go1.12.7 linux/amd64
您的注释没有使用正确的语法。 gorm-属性-value 对的分隔符是 而不是
comma
,而是semi-colon
。 https://github.com/jinzhu/gorm/blob/master/model_struct.go#L644Date.Scan()
的实现必须使用指针接收器,否则会丢失更新后的值。 https://github.com/jinzhu/gorm/blob/0fd395ab37aefd2d50854f0556a4311dccc6f45a/scaner_test.go#L57在我的测试中,
Scan
方法收到time.Time
,因此不需要nil
案例,也不需要string
案例(可能是数据库驱动程序的问题)。但是,更重要的是,我添加了一个default
案例来捕获不受支持的类型。Scan
方法涉及将从数据库读取的数据分配给您提供的对象。评论它与您在上次 sql 结果复制粘贴中演示的缺失日期问题无关。我认为这在某种程度上与我之前提到的问题有关。我听取了所有错误以确保一切正常。
这是我的测试代码,
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 图像,以便将来可以对同一版本进行尝试。
启用 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 checks if the field is sql.Scanner
and then set the IsNormal
field, which is used in query creation phase. if the field is not a sql.Scanner
, the IsNormal
is false so it's ignored here
所以,你的问题的答案是肯定的,即使你不需要扫描它也必须实现一个Scan
方法。