Go泛型实战之实现gorm单表crud.md

2022/6/7 Go

# Go泛型

大家都知道,在go1.18 中支持了泛型。

正好我最近在做一个新的项目,想着能不能简单地试用一下 ,于是我打算从gorm下手,开始编写一个支持泛型的单表curd。

# 传统模式

在有泛型之前,我们写单表的crud都是定义一个model,接着在每个model结构体中定义对应的select/update/delete等方法。虽然可以二次封装gorm的基础方法,但每个model都需要额外定义这些方法,显得比较麻烦。

当然也有可能是我姿势不对,没摸清楚门路

# 泛型模式

用过MybatisPlus的都知道,通过定义一个集成自基础Mapper的Mapper比如UserMapper ,我们就可以拥有Mapper对象的所有基础CRUD方法,这对我来说太诱人(太方便了),加上Spring的自动注入,导致编写一个CRUD方法十分方便。

有了这个参考,我打算也在go里面尝试一下类似的模式。

那关于泛型的介绍,我就不多说了。大家可以看看其他的文章~

# 定义Mapper

type Curd[T any] struct {
}
1
2

这里用any类型是为了通用,可能定义的model使用了gorm.Model,但它毕竟不是gorm.Model类型,所以这里用不了gorm.Model。这里也是可以用接口类型的,如果要取出结构体的字段,这时候就可以定义以下这种接口:

type IModel interface {
   GetId() int
}
1
2
3

# 实现最基本的SelectById方法

这里以通过主键查询数据为例,我们写一个最基本的查询相关方法:

func (c *Curd[T]) SelectById(id int) (*T, error) {
	var t T
	if err := conn.Model(&t).Find(&t, "id = ?", id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil
		}
		return nil, err
	}
	return &t, nil
}
1
2
3
4
5
6
7
8
9
10

这里我写的比较朴素,首先定义一个T类型结构体的数据,接着根据id查找到对应的数据t,最后根据是否有err,决定是否返回这个结构体的指针。

好处是:查询不到数据的时候,不会返回空结构体,而是nil,序列化为JSON以后,不会出现一个空的结构体,而是nil

# 再试下Create

我们再来编写一个Create方法:

func (c *Curd[T]) Insert(model *T) error {
	return conn.Model(model).Create(model).Error
}
1
2
3

# 这个就更赖皮了,直接通过传入的方法添加数据,几乎没有二次处理。

上述的"Mapper"就已经有了添加/查询功能,我们来简单尝试下。

# 定义User

package model

import "github.com/jinzhu/gorm"

type User struct {
	gorm.Model
	Name string `json:"name" gorm:"type:varchar(12)"`
	From string `json:"from" gorm:"type:varchar(12)"`
}
1
2
3
4
5
6
7
8
9

# 添加dao结构体

type UserDao struct {
	mapper.Curd[model.User]
}

1
2
3
4

# 测试一下

dao特别简单,通过组合Curd[model]结构体即可。这样UserDao就能使用Curd的Select和Create方法了,我们来试试吧!

这里我们调用了Insert方法,插入了一个User。(上面的迁移语句可以忽略,因为我本地环境是剥离版的)

可以看到,没有报错,我们去数据库检查下数据:

接着我们执行下select方法:

接着我们执行下Select,因为刚才插入的数据id为1,所以我查询了id为1和33的数据,可以看到2个结果分别为数据和nil,也就是说查询也ok了。

但是其实我们并没有为UserDao编写对应的查询/插入方法,却获得了这些方法,这也许就是泛型的魅力吧。

今天的内容就先介绍到这里,感兴趣的小伙伴可以自己实现完整的代码哟。