自定义类型在做数据库查询和插入操作时,可以通过实现 Scanner / Valuer 接口,使我们在做db的增删查改时更加顺畅。
现实中遇到的问题
在做后台系统时,有些表中的字段是定制化的,例如:
1
2
type Day time . Time // 以天为单位标记
type LocaleTime time . Time // 本地格式化的时间, 存在 0000-00-00 00:00:00 值
为什么需要给这些类型重定义?
我这里的原因是为了在给下游提供JSON接口时输出标准化的值。例如,对于Day 类型,需要输出 “2021-01-11”,对于 LocaleTime 需要输出的是 “2021-01-11 12:41:01”。
对于JSON 的格式化,使用的是如下接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const dayFormat = "2006-01-02"
// 格式化JSON 解析
func ( t * Day ) UnmarshalJSON ( data [] byte ) ( err error ) {
now , err := time . ParseInLocation ( `"` + dayFormat + `"` , string ( data ), time . Local )
* t = Day ( now )
return
}
// 格式化JSON 编码
func ( t Day ) MarshalJSON () ([] byte , error ) {
b := make ([] byte , 0 , len ( dayFormat ) + 2 )
b = append ( b , '"' )
b = time . Time ( t ). AppendFormat ( b , dayFormat )
b = append ( b , '"' )
return b , nil
}
const localTimeFormat = "2006-01-02 15:04:05"
func ( t * LocalTime ) UnmarshalJSON ( data [] byte ) ( err error ) {
now , err := time . ParseInLocation ( `"` + localTimeFormat + `"` , string ( data ), time . Local )
* t = LocalTime ( now )
return
}
func ( t LocalTime ) MarshalJSON () ([] byte , error ) {
b := make ([] byte , 0 , len ( localTimeFormat ) + 2 )
b = append ( b , '"' )
b = append ( b , [] byte ( t . String ()) ... )
//b = time.Time(t).AppendFormat(b, localTimeFormat)
b = append ( b , '"' )
return b , nil
}
func ( t LocalTime ) String () string {
if time . Time ( t ). IsZero () {
return "0000-00-00 00:00:00"
}
return time . Time ( t ). Format ( localTimeFormat )
}
解决了JSON 编解码的问题,但是对于DB插入和查询却总是有问题。
如何解决
经过翻看接口文档,其实解决方式和 json.UnmarshalJSON
/ json.MarshalJSON
接口类似。只要实现该类型的Valuer/Scanner接口即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func ( t Day ) Value () ( driver . Value , error ) {
tTime := time . Time ( t )
return tTime . Format ( "2006/01/02 15:04:05" ), nil
}
func ( t * Day ) Scan ( v interface {}) error {
switch vt := v .( type ) {
case time . Time :
* t = Day ( vt )
case string :
tTime , _ := time . Parse ( "2006/01/02 15:04:05" , vt )
* t = Day ( tTime )
}
return nil
}
func ( t LocalTime ) Value () ( driver . Value , error ) {
if time . Time ( t ). IsZero () {
return "0000-00-00 00:00:00" , nil
}
return time . Time ( t ), nil
}
func ( t * LocalTime ) Scan ( v interface {}) error {
switch vt := v .( type ) {
case time . Time :
* t = LocalTime ( vt )
case string :
tTime , _ := time . Parse ( "2006/01/02 15:04:05" , vt )
* t = LocalTime ( tTime )
default :
return nil
}
return nil
}
接口学习
下面,具体学习下两个接口。
Scanner 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Value interface {}
type Valuer interface {
// Value returns a driver Value.
Value () ( Value , error )
}
func IsValue ( v interface {}) bool {
if v == nil {
return true
}
switch v .( type ) {
case [] byte , bool , float64 , int64 , string , time . Time :
return true
}
return false
}
在sql的Exec 和 Query 时,需要将入参转换为各db驱动包支持的数据类型。而Valuer 是将值转换为 driver.Value 类型的接口定义。
因此,对于自定义的时间转义,可以转义为一个 time.Time 类型,或者一个字符串类型。
Valuer 接口
1
2
3
type Scanner interface {
Scan ( src interface {}) error
}
在数据查询结果中,需要将查询结果映射为go支持的数据类型。在实现时,首先会把所有的数据都转换为 int64, float64, bool, []byte, string, time.Time, nil 几种类型,然后调用目标类型的Scan方法赋值。实现Scanner 接口时,入参即为这些类型中的一种,仅需把入参转为我们的变量即可。