准备工作
建立数据库连接
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的更多功能与源码还未深刻研究学习。本文暂时只展示如何简单的应用自定义类型方法,待后续有空闲时间学习其源码与官方文档,在做更深刻学习。