起步
想在使用 GORM 时使用自定义类型必然事出有因,一般可有以下两种方式:
- 方法 1:
type MyString string
- 方法 2:
type MyString struct {
string
}
当需求比较简单时,可采取方法1,也就是类型别名;如果需求复杂,就不得不把数据字段嵌入自定义结构体中。字段是否匿名并不重要,主要是用来承载目的数据。
单单把数据类型定义了还不够,还需要实现两个方法,你把这理解为一种协议即可。
// 写入数据库之前,对数据做类型转换
func (s MyString) Value() (driver.Value, error) {
...
}
// 将数据库中取出的数据,赋值给目标类型
func (s *MyString) Scan(v interface{
}) error {
...
}
下面将结合我在实际开发遇到的业务场景,讲解为什么需要自定义类型,以及如何去实现上述的两个方法。
方法1:类型别名
场景 1
第一个场景:我需要自定义时间的显示格式。
当我的 model 嵌入 gorm.Model 时,会多四个字段,分别是:id, created_at, updated_at, deleted_at。
type Plan struct {
gorm.Model
Name string `gorm:"column:name"`
}
我面对的需求是,把数据从数据库中取出来,并按照规定的格式显示时间,最后返回给前端(需要 JSON 处理)。当然,我比较懒,希望直接取出数据,立马返给前端,时间的格式还是我期望的那样。为简便起见,这里只用到 created_at,name 两个字段。
先定义一个返给前端的数据结构:
type MyTime time.Time
// 返回给前端的数据结构
type Resp struct {
CreatedAt MyTime `gorm:"column:created_at"`
Name string `gorm:"column:name"`
}
查询数据库代码如下。同时我用 json.Marshal 方法将结构体转换成 json 字符串,相当于模拟了将数据传递给前端的一个过程。
var resp Resp
db.Model(&Plan{
}).Select("created_at, name").Limit(1).Scan(&resp)
data, _ := json.Marshal(resp)
log.Println(string(data))
然而日志输出不是我们想看到的:2020/02/16 19:21:28 {"CreatedAt":{},"Name":"早饭"}
。
这里还需要注意程序并没有报错。没报错是因为 MyTime 是 time.Time 类型的别名,两个类型之间允许相互转换。但是为什么输出是一个空值呢?
MyTime 作为 time.Time 的别名,但是并没有继承 time.Time 的方法,也就不支持 json.Marshal 转换。所以还需要为 MyTime 绑定 MarshalJSON 方法。
func (t MyTime) MarshalJSON() ([]byte, error) {
tTime := time.Time(t)
tStr := tTime.Format("2006/01/02 15:04:05") // 设置格式
// 注意 json 字符串风格要求
return []byte(fmt.Sprintf("\"%v\"", tStr)), nil