gorm基础-8.自定义数据类型

很多情况下我们存储到数据库中的数据是多变的

例如我需要存储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))

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值