Gorm 预加载及输出处理(二)- 查询输出处理

这一篇先解决前两个问题。

模型结构体中指针类型的应用

先来看一个上一篇中埋下的坑,回顾下 User 模型的定义:

// 用户模型 type User struct { gorm.Model Username string `gorm:"type:varchar(20);not null;unique"` Email string `gorm:"type:varchar(64);not null;unique"` Role string `gorm:"type:varchar(32);not null"` Active uint8 `gorm:"type:tinyint unsigned;default:1"` Profile Profile `gorm:"foreignkey:UserID;association_autoupdate:false"` }

其中 Active 字段类型为 uint8 类型,表示该用户是否处于激活状态,0 为未激活,1 为已激活,默认值为 1,看起来好像没什么问题,如果要创建一个默认未激活的用户,自然是指定 Active 的值为 0,然后调用 Create 方法即可。但是,你会发现数据库中写入的仍然是 1,一起来看下 Gorm 使用的 sql 语句:

INSERT INTO `user` (`created_at`,`updated_at`,`deleted_at`,`username`,`email`,`role`) VALUES ('2020-03-15 12:41:14','2020-03-15 12:41:14',NULL,'test14','aaa@bbb.com','admin')

根本就没有往 active 列中插入数据,然后就使用了默认值 1。这是 Gorm 的写入机制引起的,Gorm 不会将零值写入数据库中,部分零值列举如下:

false // bool 0 // integer 0.0 // float "" // string nil // pointer, function, interface, slice, channel, map

解决此问题也很简单,将字段定义为对应的指针类型,赋值时也传指针即可,只要传的值不为 nil,即可正常写入数据库。

现调整 User 模型定义如下:

type User struct { ... Active *uint8 `gorm:"type:tinyint unsigned;default:1"` ... }

到这里,应该已经清楚 Gorm 模型字段定义中指针类型的应用场景了,即任何需要保存零值的字段,都应定义为指针类型。利用该特性,顺带把上一篇中直接查询 User 输出空值 Profile 结构体的问题一并解决掉。只要将 User 模型中 Profile 字段的类型修改为 Profile 的指针类型即可:

// 用户模型 type User struct { ... Profile *Profile `gorm:"foreignkey:UserID;association_autoupdate:false"` }

对应的,在创建 User 的时候,Profile 字段接收的也要是指针类型。这样处理以后,当直接查询 User 而不关联查询 Profile 时,User 中 Profile 字段将为 nil,而不是之前讨厌的空值结构体,清爽了很多不是吗。

自定义输出

Gorm 默认会查询模型的所有字段并按模型定义的结构返回数据,在实际应用中,往往并不需要输出全部字段,这就需要对输出字段进行过滤,通常有两种方式:

在查询时指定查询字段;

默认查询所有字段,序列化时对字段进行过滤;

第一种方式非常直观简单,要什么,查什么,输出什么,在输出比较固定的场景中非常实用。其缺点也很明显,就是灵活性不高,如果多个接口查一张表,但每个接口所需要的字段又不一样,那么就得为每个接口写一个独立的查询来实现这个需求,这显然不符合“少即是多”、“高复用”的编程思想。

第二种方式在 Model层(查询阶段)不做过滤或只做基础过滤,通过接口对 Service层(逻辑层)提供一份较为完整的数据,Service 层将数据按需映射到自定义输出结构体上然后序列化输出。这样,当需要反复修改输出结构时,Model 层几乎不用做任何改动,只需 Service 层调整输出结构并序列化即可,可最大限度将逻辑和源数据分离,便于维护。

下面通过实际应用来介绍如何自定义输出结构并序列化。

场景

用户列表,输出所有用户,并且用户数据只包含 id,username,role 字段;
用户详情,输出当前用户,除上述数据,还应包含 Profile 中的 Nickname,Phone 字段;

自定义输出结构体

这一步只要按需求创建对应结构体即可,直接上代码:

// 自定义用户输出结构 type CustomUser struct { ID uint Username string Role string Profile *CustomProfile } // 自定义用户信息输出结构 type CustomProfile struct { Nickname string Phone string } JSON Tag 的简单应用 - 自定义字段名,去掉空值字段

默认情况下,结构体序列化后的字段名和结构体的字段名保持一致,如在结构体中定义了对外公开的字段,字段名首字母都是大写的,JSON 序列化后得到的也是首字母大写的字段名,并不符合日常开发习惯。

其实 go 提供了在结构体中使用 JSON Tag 定制序列化输出的功能,本文仅使用了“自定义字段名”和“忽略空值字段”两个功能,详见 go 标准库 encoding/json 文档。

现在利用 JSON Tag 来改造上面两个结构体,这里要做的只有两步:

把字段名全部改为小写;

对 CustomUser 中的 Profile 设置 omitempty 标签,即当 Profile 的值为 nil 时,不输出 Profile 字段;

代码如下:

// 自定义用户输出结构 type CustomUser struct { ID uint `json:"id"` Username string `json:"username"` Role string `json:"role"` Profile *CustomProfile `json:"profile,omitempty"` } // 自定义用户信息输出结构 type CustomProfile struct { Nickname string `json:"nickname"` Phone string `json:"phone"` }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzxgyp.html