GORM 是GO语言中一款强大友好的ORM框架,但在使用过程中内置的数据类型不能满足以下两个需求,如下:
time.Time
类型返回的是2023-10-03T09:12:08.53528+08:00
这种字符串格式,需要额外处理,我们更希望默认的是是2023-10-03 09:12:08
这种可读性更高的格式- 有些数据字段需要存储数组形式,如下
Article
中Tags
字段希望保存不确定个字符串。直接保存会提示[error] unsupported data type: &[]
官方提供了
Scanner
和Valuer
两个接口,来满足自定义数据的存储、提取,本文记录上述两种结构解决方法。
type Article struct {
Tags []string
}
自定义时间类型
自定义时间类型需满足以下两个需求:
- 返回
2006-01-02 15:04:05
格式CreatedAt
和UpdatedAt
使用时能按GORM
规范自动填充当前时间
默认的
time.Time
是能被gorm
自动存储,取出到结构体的,返回2023-10-03T09:12:08.53528+08:00
格式原因是在于json
序列化时,这里解决方案是自定义一个数据结构,添加JSON Marshal接口
,但是自定义的数据类型gorm
不能识别,所以要额外添加gorm
的Scanner
和Valuer
两个接口
type CustomTime time.Time
// GORM Scanner 接口, 从数据库读取到类型
func (t *CustomTime) Scan(value any) error {
if v, ok := value.(time.Time); !ok {
return errors.Errorf("failed to unmarshal CustomTime value: %v", value)
} else {
*t = CustomTime(v)
return nil
}
}
// GORM Valuer 接口, 保存到数据库
func (t CustomTime) Value() (driver.Value, error) {
if time.Time(t).IsZero() {
return nil, nil
}
return time.Time(t), nil
}
// JSON Marshal接口,CustomTime结构体转换为json字符串
func (t *CustomTime) MarshalJSON() ([]byte, error) {
t2 := time.Time(*t)
return []byte(fmt.Sprintf(`"%v"`, t2.Format("2006-01-02 15:04:05"))), nil
}
自定义字符串数组
代码比较简单,直接定义一个类型实现
Scanner
和Valuer
两个接口,使用中将列定义为Strings
类型即可
type Strings []string
func (s *Strings) Scan(value any) error {
v, _ := value.(string)
return json.Unmarshal([]byte(v), s)
}
func (s Strings) Value() (driver.Value, error) {
b, err := json.Marshal(s)
return string(b), err
}
测试与完整代码
测试代码
package main
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
"github.com/pkg/errors"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
type Strings []string
func (s *Strings) Scan(value any) error {
v, _ := value.(string)
return json.Unmarshal([]byte(v), s)
}
func (s Strings) Value() (driver.Value, error) {
b, err := json.Marshal(s)
return string(b), err
}
type CustomTime time.Time
// GORM Scanner 接口, 从数据库读取到类型
func (t *CustomTime) Scan(value any) error {
if v, ok := value.(time.Time); !ok {
return errors.Errorf("failed to unmarshal CustomTime value: %v", value)
} else {
*t = CustomTime(v)
return nil
}
}
// GORM Valuer 接口, 保存到数据库
func (t CustomTime) Value() (driver.Value, error) {
if time.Time(t).IsZero() {
return nil, nil
}
return time.Time(t), nil
}
// JSON Marshal接口,CustomTime结构体转换为json字符串
func (t *CustomTime) MarshalJSON() ([]byte, error) {
t2 := time.Time(*t)
return []byte(fmt.Sprintf(`"%v"`, t2.Format("2006-01-02 15:04:05"))), nil
}
// fmt.Printf, 【可选方法】
func (t CustomTime) String() string {
return time.Time(t).Format("2006-01-02 15:04:05")
}
type Article struct {
ID uint `gorm:"primaryKey"`
Tags Strings
CreatedAt CustomTime
UpdatedAt CustomTime
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Article{})
db.Create(&Article{Tags: []string{"go", "java"}})
var article Article
db.Last(&article)
fmt.Printf("article is: %v\n", article)
b, _ := json.Marshal(&article)
fmt.Printf("article json is: %s\n", string(b))
time.Sleep(time.Second * 30)
article.Tags = append(article.Tags, "python")
db.Save(&article)
db.Last(&article)
fmt.Printf("updated article is: %v\n", article)
b, _ = json.Marshal(&article)
fmt.Printf("updated article json is: %s\n", string(b))
}
测试结果
字符串数组
自定义时间,可以看到满足
2006-01-02 15:04:05
格式输出,以及时间自动添加和更新