如果我们只使用一张表来存储这个Blog数据结构的话,通常的做法是使用Json类型和数组类型的字段来存储。
目前Mysql和Postgresql的新版都已支持Json类型和数组类型。
本文所使用的数据库是Postgresql,具体版本不影响,Mysql的话可能需要新版的
可是在gorm里面我们直接将这个结构体进行模型迁移的话,会遇到不少问题!
下面我们来分析一下:
如果我们之间将这个结构体进行模型迁移的话,就会报下面的错误
可以看到,gorm是不支持数组类型的,咋办呢?很简单,只需要做一个简单的改动,就能让它支持数据类型。为Tags字段加上标签gorm:"type:varchar(255)[]"就不会报错了。
type Blog struct {
ID int `json:"id" gorm:"primaryKey"`
Tags []string `json:"tags" gorm:"type:varchar(255)[]"`
Author Author `json:"author"`
Title string `json:"title"`
Content string `json:"content"`
}
但此时仍然无法进行迁移,还会报错:
[error] invalid field found for struct main.Blog's field Author: define a valid foreign key for relations or implement the Valuer/Scanner interface
2024/01/20 00:54:37 invalid field found for struct main.Blog's field Author: define a valid foreign key for relations or implement the Valuer/Scanner interface
这是因为gorm不能直接迁移Bolg中的Author字段。解决也很简单,跟上面一样,只需要添加一个gorm:"type:json"标签即可。
type Blog struct {
ID int `json:"id" gorm:"primaryKey"`
Tags []string `json:"tags" gorm:"type:varchar(255)[]"`
Author Author `json:"author" gorm:"type:json"`
Title string `json:"title"`
Content string `json:"content"`
}
此时就能迁移成功了!查看数据库,表也建起来了!
不过现在还没结束,我们再来尝试读取和写入的操作:
func InsertData() {
author := Author{
Name: "John Doe",
Email: "1992@qq.com",
}
blog := Blog{
Tags: []string{"tag1", "tag2"},
Author: author,
Title: "Hello, World!",
Content: "This is a sample blog post.",
}
result := DB.Create(&blog)
if result.Error != nil {
log.Fatal(result.Error)
}
}
试着插入数据,执行完代码后,发现还是报错:
原因是因为tags字段不支持直接插入切片类型的数据,这里有一个比较简单的做法,就是把切片类型修改成一个第三方库提供的类型,具体后面会给出完整代码:
type Blog struct {
ID int `json:"id" gorm:"primaryKey"`
Tags pq.StringArray `json:"tags" gorm:"type:varchar(255)[]"`
Author Author `json:"author" gorm:"type:json"`
Title string `json:"title"`
Content string `json:"content"`
}
现在再来试试插入数据,发现已经插入成功,数据库表里也有数据了。
我们再试试读数据:
相信也能猜到了,很显然,还是会出现报错,而这次报错的原因,是出在author字段无法scan到对应的结构体里面。
2024/01/20 01:14:26 sql: Scan error on column index 2, name "author": unsupported Scan, storing driver.Value type []uint8 into type *main.Author
咋办呢?其实说白了就是gorm不认识我们的Author类型,而我们要做的就是需要让gorm认识,而相关的文档,在官网里有提到自定义数据类型。
这里直接贴出代码,就是给我们的Author实现两个规定的方法:
其实这里做了很简单的事情,就是在数据库插入数据前,把我们的结构体序列化成字节数组,在数据库查询到数据时,将对应的数据反序列成对应的结构体:
// Scan 将数据库中的值转换为Author类型
func (o *Author) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("failed to unmarshal Author value")
}
var config Author
err := json.Unmarshal(b, &config)
if err != nil {
return err
}
*o = config
return nil
}
// Value 将Author类型转换为数据库可存储的值
func (o Author) Value() (driver.Value, error) {
return json.Marshal(o)
}
此时,就能可读可写了!
本文完整代码:
package main
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"github.com/lib/pq"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"log"
)
type Author struct {
Name string
Email string
}
// Scan 将数据库中的值转换为Author类型
func (o *Author) Scan(value interface{}) error {
b, ok := value.([]byte)
if !ok {
return errors.New("failed to unmarshal Author value")
}
var config Author
err := json.Unmarshal(b, &config)
if err != nil {
return err
}
*o = config
return nil
}
// Value 将Author类型转换为数据库可存储的值
func (o Author) Value() (driver.Value, error) {
return json.Marshal(o)
}
type Blog struct {
ID int `json:"id" gorm:"primaryKey"`
Tags pq.StringArray `json:"tags" gorm:"type:varchar(255)[]"`
Author Author `json:"author" gorm:"type:json"`
Title string `json:"title"`
Content string `json:"content"`
}
func (Blog) TableName() string {
return "t_blog"
}
var DB *gorm.DB
func InitDB() {
var err error
dsn := "user=postgres password=xxx dbname=test port=5432 sslmode=disable TimeZone=Asia/Shanghai"
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
//数据库迁移
err = DB.AutoMigrate(Blog{})
if err != nil {
log.Fatal(err)
}
}
func InsertData() {
author := Author{
Name: "John Doe",
Email: "1992@qq.com",
}
blog := Blog{
Tags: []string{"tag1", "tag2"},
Author: author,
Title: "Hello, World!",
Content: "This is a sample blog post.",
}
result := DB.Create(&blog)
if result.Error != nil {
log.Fatal(result.Error)
}
}
func ReadData() {
blog := Blog{}
result := DB.First(&blog)
if result.Error != nil {
log.Fatal(result.Error)
}
fmt.Printf("ID: %d\n", blog.ID)
fmt.Printf("Tags: %v\n", blog.Tags)
fmt.Printf("Author: %v\n", blog.Author)
fmt.Printf("Title: %s\n", blog.Title)
fmt.Printf("Content: %s\n", blog.Content)
}
func main() {
InitDB()
InsertData()
ReadData()
}