如果我们只使用一张表来存储这个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: "[email protected]", } 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: "[email protected]", } 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 ", blog.ID) fmt.Printf("Tags: %v ", blog.Tags) fmt.Printf("Author: %v ", blog.Author) fmt.Printf("Title: %s ", blog.Title) fmt.Printf("Content: %s ", blog.Content) } func main() { InitDB() InsertData() ReadData() }