Gorm 新功能 Serializer 尝鲜

3,127 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

近期观察到 gorm.io/gorm 新增了一个由 jinzhu 亲自操刀,很强大的feature:Serializer。可能由于时间较紧,gorm的官方文档暂时还没有更新,自己先来尝个鲜。

示例场景

先回顾一下以前我们是怎样定义 gorm model 的:

模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成

from gorm.io/zh_CN/docs/…

日常开发中,很多时候我会以 Go 基本数据类型为主来定义一个纯粹给 gorm 使用的 model,字段和数据表 schema 一一对应。比如我需要开发一个应用,业务上看需要将应用ID,配置存入数据库。

type Application struct {
	gorm.Model
	AppID  int64  `gorm:"column:app_id" json:"app_id"`
	Config string `gorm:"column:config" json:"config"`
}

这里的 Config 字段,对应到底层数据表是一个 text 类型,如下

`config` text COLLATE utf8mb4_general_ci COMMENT '配置项'

但实际上,config 本身是一个 json 序列化的字符串,包含了这个应用的相关配置信息。

type ApplicationConfig struct {
	Name 		string
	AvatarURL 	string
	WebURL 		string
}

在实际存储数据时,我们会先把获取到的 ApplicationConfig 序列化为字符串,塞到 Application 的 Config 字段

package main

import (
	"encoding/json"
	"log"
	"os"
	"time"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

type Application struct {
	gorm.Model
	AppID  int64  `gorm:"column:app_id" json:"app_id"`
	Config string `gorm:"column:config" json:"config"`
}

type ApplicationConfig struct {
	Name      string
	Desc      string
	AvatarURL string
	WebURL    string
}

func main() {
	// initialize gorm
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold:             time.Second, // Slow SQL threshold
			LogLevel:                  logger.Info, // Log level
			IgnoreRecordNotFoundError: true,        // Ignore ErrRecordNotFound error for logger
			Colorful:                  true,        // Disable color
		},
	)
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: newLogger,
	})
	if err != nil {
		panic("failed to connect database")
	}
	// Migrate the schema
	db.AutoMigrate(&Application{})

	// Construct Biz Config
	bizModel := &ApplicationConfig{
		Name:      "testName",
		Desc:      "testDesc",
		AvatarURL: "https://image.url",
		WebURL:    "http://test.web.url",
	}
	val, _ := json.Marshal(bizModel)

	// call gorm.DB.Create
	db.Create(&Application{
		AppID:  1,
		Config: string(val),
	})
}

执行demo后发现打印出的 INSERT 语句如下:

INSERT INTO `applications` (`created_at`,`updated_at`,`deleted_at`,`app_id`,`config`) VALUES ("2022-02-23 17:29:41.466","2022-02-23 17:29:41.466",NULL,1,"{\"Name\":\"testName\",\"Desc\":\"testDesc\",\"AvatarURL\":\"https://image.url\",\"WebURL\":\"http://test.web.url\"}") RETURNING `id`

使用 Serializer 简化

从上面的例子可以看到,我们要做到将一个 json 字符串插入数据库,需要提前自行序列化,再通过为 gorm 定义的 model 来完成 Create 操作。

有没有办法简化呢?有!Serializer 就是来帮你完成序列化相关操作的。

有了 Serializer,我们无需再将原有的序列化结构进行 json 序列化,再塞到 model中,而是直接在 model 中将需要序列化的字段加上 serializer tag。看下效果:

原来的 Config 字段定义:

Config string `gorm:"column:config" json:"config"`

改为

Config *ApplicationConfig `gorm:"column:config;serializer:json" json:"config"`

原来的 Create 操作:

	bizModel := &ApplicationConfig{
		Name:      "testName",
		Desc:      "testDesc",
		AvatarURL: "https://image.url",
		WebURL:    "http://test.web.url",
	}
	val, _ := json.Marshal(bizModel)

	db.Create(&Application{
		AppID:  1,
                Config: string(val),
	})

改为

db.Create(&Application{
		AppID:  1,
		Config: &ApplicationConfig{
			Name:      "testName",
			Desc:      "testDesc",
			AvatarURL: "https://image.url",
			WebURL:    "http://test.web.url",
		},
	})

这次生成的 INSERT语句如下:

INSERT INTO `applications` (`created_at`,`updated_at`,`deleted_at`,`app_id`,`config`) VALUES ("2022-02-23 17:42:59.57","2022-02-23 17:42:59.57",NULL,1,"{\"Name\":\"testName\",\"Desc\":\"testDesc\",\"AvatarURL\":\"https://image.url\",\"WebURL\":\"http://test.web.url\"}") RETURNING `id`

gorm自动处理了json序列化的操作,生成的 INSERT语义跟原来的一模一样。从此再也不用拆分多个 model了,直接使用 serializer tag。

不仅仅是单独的 struct 嵌套,serializer 还支持对 slice, map 进行序列化,进行序列化的方法也不限制于json,官方目前提供了 json, unixtime, gob 的支持,同学们也可以自行提PR 支持更多序列化的方法。

gorm 对于 serializer tag 的单测:github.com/go-gorm/gor…