很多情况下我们存储到数据库中的数据是多变的
例如我需要存储json或者是数组
然后很多数据库并不能直接存储这些数据类型,我们就需要自定义数据类型
自定义的数据类型必须实现 Scanner 和 Valuer 接口,以便让 GORM 知道如何将该类型接收、保存到数据库
gorm中自定义数据类型无外乎就两个方法
在数据入库的时候要转换为什么数据,已经出库的时候数据变成什么样子
存储json
存储json可能是经常使用到的
我们需要定义一个结构体,在入库的时候,把它转换为[]byte类型,查询的时候把它转换为结构体
type Info struct {
Status string `json:"status"`
Addr string `json:"addr"`
Age int `json:"age"`
}
// Scan 从数据库中读取出来
func (i *Info) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
}
err := json.Unmarshal(bytes, i)
return err
}
// Value 存入数据库
func (i Info) Value() (driver.Value, error) {
return json.Marshal(i)
}
type AuthModel struct {
ID uint
Name string
Info Info `gorm:"type:string"`
}
func main() {
DB.AutoMigrate(&AuthModel{})
}
插入数据
DB.Debug().Create(&AuthModel{
Name: "枫枫",
Info: Info{
Status: "success",
Addr: "湖南省长沙市",
Age: 21,
},
})
// INSERT INTO `auth_models` (`name`,`info`) VALUES ('枫枫','{"status":"success","addr":"湖南省长沙市","age":21}')
查询数据
var auth AuthModel
DB.Take(&auth, "name = ?", "枫枫")
fmt.Println(auth)
存储数组
很多时候存储数组也是很常见的
最简单的方式就是存json
type Array []string
// Scan 从数据库中读取出来
func (arr *Array) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
}
err := json.Unmarshal(bytes, arr)
return err
}
// Value 存入数据库
func (arr Array) Value() (driver.Value, error) {
return json.Marshal(arr)
}
type HostModel struct {
ID uint `json:"id"`
IP string `json:"ip"`
Ports Array `gorm:"type:string" json:"ports"`
}
func main() {
//DB.AutoMigrate(&HostModel{})
//DB.Create(&HostModel{
// IP: "192.168.200.21",
// Ports: []string{"80", "8080"},
//})
var host HostModel
DB.Take(&host, 1)
fmt.Println(host)
}
当然,也可以用字符串拼接,例如 |
, =
, ,
, ;
type Array []string
// Scan 从数据库中读取出来
func (arr *Array) Scan(value interface{}) error {
data, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprintf("解析失败: %v %T", value, value))
}
*arr = strings.Split(string(data), "|")
return nil
}
// Value 存入数据库
func (arr Array) Value() (driver.Value, error) {
return strings.Join(arr, "|"), nil
}
当然,拼接的字符串不能是输入字符串中存在的
枚举类型
枚举1.0
很多时候,我们会对一些状态进行判断,而这些状态都是有限的
例如,主机管理中,状态有 Running 运行中, OffLine 离线, Except 异常
如果存储字符串,不仅是浪费空间,每次判断还要多复制很多字符,最主要是后期维护麻烦
type Host struct {
ID uint
Name string
Status string
}
func main() {
host := Host{}
if host.Status == "Running" {
fmt.Println("在线")
}
if host.Status == "Except" {
fmt.Println("异常")
}
if host.Status == "OffLine" {
fmt.Println("离线")
}
}
后来,我们知道了用常量存储这些不变的值
type Host struct {
ID uint
Name string
Status string
}
const (
Running = "Running"
Except = "Except"
OffLine = "OffLine"
)
func main() {
host := Host{}
if host.Status == Running {
fmt.Println("在线")
}
if host.Status == Except {
fmt.Println("异常")
}
if host.Status == OffLine {
fmt.Println("离线")
}
}
虽然代码变多了,但是维护方便了
但是数据库中存储的依然是字符串,浪费空间这个问题并没有解决
枚举2.0
于是想到使用数字表示状态
type Host struct {
ID uint
Name string
Status int
}
const (
Running = 1
Except = 2
OffLine = 3
)
func main() {
host := Host{}
if host.Status == Running {
fmt.Println("在线")
}
if host.Status == Except {
fmt.Println("异常")
}
if host.Status == OffLine {
fmt.Println("离线")
}
}
但是,如果返回数据给前端,前端接收到的状态就是数字,不过问题不大,前端反正都要搞字符映射的
因为要做颜色差异显示
但是这并不是后端偷懒的理由
于是我们想到,在json序列化的时候,根据映射转换回去
type Host struct {
ID uint `json:"id"`
Name string `json:"name"`
Status int `json:"status"`
}
func (h Host) MarshalJSON() ([]byte, error) {
var status string
switch h.Status {
case Running:
status = "Running"
case Except:
status = "Except"
case OffLine :
status = "OffLine"
}
return json.Marshal(&struct {
ID uint `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
}{
ID: h.ID,
Name: h.Name,
Status: status,
})
}
const (
Running = 1
Except = 2
OffLine = 3
)
func main() {
host := Host{1, "枫枫", Running}
data, _ := json.Marshal(host)
fmt.Println(string(data)) // {"id":1,"name":"枫枫","status":"Running"}
}
这样写确实可以实现我们的需求,但是根本就不够通用,凡是用到枚举,都得给这个Struct实现MarshalJSON
方法
枚举3.0
于是类型别名出来了
type Status int
func (status Status) MarshalJSON() ([]byte, error) {
var str string
switch status {
case Running:
str = "Running"
case Except:
str = "Except"
case OffLine:
str = "Status"
}
return json.Marshal(str)
}
type Host struct {
ID uint `json:"id"`
Name string `json:"name"`
Status Status `json:"status"`
}
const (
Running Status = 1
Except Status = 2
OffLine Status = 3
)
func main() {
host := Host{1, "枫枫", Running}
data, _ := json.Marshal(host)
fmt.Println(string(data)) // {"id":1,"name":"枫枫","status":"Running"}
}
嗯,代码简洁了不少,在使用层面已经没有问题了
在grom中使用
type Status int
func (s Status) MarshalJSON() ([]byte, error) {
return json.Marshal(s.String())
}
func (s Status) String() string {
var str string
switch s {
case Running:
str = "Running"
case Except:
str = "Except"
case OffLine:
str = "Status"
}
return str
}
const (
Running Status = 1
OffLine Status = 2
Except Status = 3
)
type Host struct {
ID uint `json:"id"`
Status Status `gorm:"size:8" json:"status"`
IP string `json:"ip"`
}
func main() {
//DB.AutoMigrate(&Host{})
//DB.Create(&Host{
// IP: "192.168.200.12",
// Status: Running,
//})
var host Host
DB.Take(&host)
fmt.Println(host)
fmt.Printf("%#v,%T\n", host.Status, host.Status)
data, _ := json.Marshal(host)
fmt.Println(string(data))
}