gorm自定义数据类型

准备工作

建立数据库连接

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
)

var db *gorm.DB

func OpenDB() {
	dsn := "root:adss123@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True&loc=Local"
	res, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	db = res
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("成功:%v\n", db)
}

实现自定义数据类型

自定义的数据类型必须实现 Scanner 和 Valuer 接口,以便让 GORM 知道如何将该类型接收、保存到数据库

例如:

type JSON json.RawMessage

// 实现 sql.Scanner 接口,Scan 将 value 扫描至 Jsonb
func (j *JSON) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
  }

  result := json.RawMessage{}
  err := json.Unmarshal(bytes, &result)
  *j = JSON(result)
  return err
}

// 实现 driver.Valuer 接口,Value 返回 json value
func (j JSON) Value() (driver.Value, error) {
  if len(j) == 0 {
    return nil, nil
  }
  return json.RawMessage(j).MarshalJSON()
}

** 实验如下 **
在没有外键,或者定义自定义类型的Value和Scan方法时,是创建不了带自定义类型的表。例如:

package test_gorm

type CInfo struct {
	Name string
	Age  int
}
type CUser struct {
	ID   uint
	Info CInfo
}

func CreateCustomize() {
	OpenDB()
	db.AutoMigrate(&CInfo{}, &CUser{})
}

在这里插入图片描述
此时我们去实现Scan与Value方法


个人理解,Value方法是将自定义结构体转译成数据库能识别储存的编码,Scan方法则是将编码转译会自定义结构体。
Value方法的简单实现:

func (c CInfo) Value() (driver.Value, error) {
	marshal, err := json.Marshal(c)
	if err != nil {
		return nil, err
	}
	return marshal, nil
}
func (c *CInfo) Scan(value interface{}) error {
	return nil
}

其中marshal是将自定义类型转译成json编码,储存在[]byte中,达到储存记录的效果。
测试储存效果:
在这里插入图片描述

func InsertCus() {
	OpenDB()
	user := &CUser{ID: 1, Info: CInfo{Name: "ylj", Age: 12}}

}

在这里插入图片描述

这里已经将转移后的编码存入user_info,但是由于数据库中的Info数据类型是BLOB类型,显示的是转译后的二进制编码。如果想让info显示具体文本,则需要在创建表的时候添加type标签,把数据类型改为text类型。


type CInfo struct {
	Name string
	Age  int
}
type CUser struct {
	ID   uint
	Info CInfo `gorm:"type:longtext"`
}

此时就可以正常显示
在这里插入图片描述

Scan方法的简单实现

func (c *CInfo) Scan(value interface{}) error {
	bytes, ok := value.([]byte)
	if !ok {
		return errors.New("不匹配")
	}
	json.Unmarshal(bytes, c)
	return nil
}

这里再获取转移后的[]byte后,利用Unmarshal转译回自定义类型,再扫描到结构体中,所以注意Scan方法的接收者必须是指针类型。
现在进行查询操作

func InsertCus() {
   OpenDB()
   user1 := &CUser{}
   db.Find(user1)
   fmt.Println(user1)
}

在这里插入图片描述
现在测试在info中再添加一个string切片,测试定义是否成功。

type CInfo struct {
	Name    string
	Age     int
	Friends []string
}
type CUser struct {
	ID   uint
	Info CInfo `gorm:"type:longtext"`
}
func InsertCus() {
	OpenDB()
	user := &CUser{ID: 2, Info: CInfo{Name: "ylj", Age: 12, Friends: []string{"1", "2", "3"}}}
	db.Create(user)
	user1 := &CUser{}
	db.Find(user1, 2)
	fmt.Println(user1)
}


在这里插入图片描述
在这里插入图片描述

发现Scan与Value都正常运作。


由于Scan与Value的更多功能与源码还未深刻研究学习。本文暂时只展示如何简单的应用自定义类型方法,待后续有空闲时间学习其源码与官方文档,在做更深刻学习。