这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」
前言
上文中,我们实现了通过go语言结构体在OpenGauss数据库上实现自动建表,在本文中,将在上文结构体的基础上,实现单条数据的查询操作。
生成sql语句
查询的基础是构建一条正确的sql语句,对于查询操作,我们直接使用SELECT *来获取行各字段,我们需要构建下面一条语句:
SELECT * FROM table WHERE id = 1;
复制代码
可以看出,我们需要实现对所有表进行查询操作,需要两个变量:
- 查询表名称
- 约束条件
对于表名,我们直接使用reflect.typeOf(model)反射出表名:
t := reflect.TypeOf(model)
tableName := strings.Split(t.String(), ".")[1]
复制代码
对于约束条件来说,作为参数直接输出即可,完成代码如下:
// First 按字段名查询单个
// odds => WHERE id = 1 || LIKE name = '%cj%' || ......
func First(model interface{}, odds string) error {
// 反射获取表名
t := reflect.TypeOf(model)
tableName := strings.Split(t.String(), ".")[1]
// 构建查询语句并交付数据库查询
sql := "SELECT * FROM " + tableName + " " + odds
return getFirst(model, sql)
}
复制代码
读取返回数据
上面构建sql语句还是挺容易的,但是要实现返回的将赋值给输出的model对象就难了,下面分几个部分进行实现:
一、将sql语句交付数据库查询
logrus.Debugln(sql)
rows, err := db.Query(sql)
if err != nil {
return err
}
defer rows.Close()
复制代码
首先,我们先使用db.Query(sql),将sql交付给openGauss数据库,然后返回一个rows对象,此时,如果没有报错的话,数据库返回的数据会被存储在rows内。
二、存储返回数据
想要读取rows中的数据,我们需要先执行rows.Next(),该函数会返回一个bool类型数据,如果可以读取到数据,则会返回true,否则返回false。
另外,换行操作也封装在rows.Next()中,因此在读取行之前,需要先执行一次rows.Next(),然后才能读到数据。
构建代码如下:
if !rows.Next() {
return errors.New("sql: Scan called without calling Next")
}
复制代码
这里我们只读首行,多行读取可以通过for循环进行,但是在赋值方面差异较大,我们后面再讨论。
然后我们在需要读取出rows中的数据之前,需要先读取rows.Columns,即行的字段,通过len()读取出字段数量,然后用该长度构建一个slice:
columns, err := rows.Columns()
if err != nil {
return err
}
复制代码
至于为什么要该数据,是因为我们需要使用rows.Scan()来读取数据,Scan函数参数如下:
可以看出需要传入的是可变参数,而传入的参数数量需要与SELECT返回回来的个数相同,否则会报错,因此我们使用slice的一个特性,使用...
也传一个可变的入参。
代码实现如下,由于Scan输入为指针类型,需要在输入前对values进行初始化:
values := make([]interface{}, len(columns))
for i := range values {
values[i] = new(string)
}
if err := rows.Scan(values...); err != nil {
return err
}
复制代码
然后,然后返回值就会保存在values数值中了。
三、构建map,转移数据
由于在slice中的数据是没有字段数据的,因此我们需要使用上面的columns和values共同构建一个map,用于存储读取到的数据,详情见代码即可。
代码实现如下:
// 4、构建map,将数组数据转移到map缓存中
m := make(map[string]interface{})
for i, column := range columns {
m[strings.ToUpper(column[:1]) + column[1:]] = *values[i].(*string)
}
复制代码
四、反射数据到原对象
然后就是查询操作中最重要的一个环节:将数据反射到原对象中。
这里使用的是反射中的reflect.ValueOf()函数,获得原对象的Value类型数据,然后通过其下的.Elem().FieldByName函数,通过字段名获取到我们需要修改字段的值位置,最后使用SetString()函数实现赋值操作。而所使用的字段名和之前一样,通过reflect.TypeOf()函数,然后遍历获取。
代码实现如下:
func getFirst(model interface{}, sql string) error {
// 5、使用反射将map映射到原结构体中
t := reflect.TypeOf(model)
for i := 0; i < t.Elem().NumField(); i++ {
field := t.Elem().Field(i)
if m[field.Name] != nil {
v := reflect.ValueOf(model).Elem().FieldByName(field.Name)
v.SetString(m[field.Name].(string))
}
}
return nil
}
复制代码
调用函数
构建一个空结构体,然后调用函数对齐赋值:
r := &models.Role{}
err := models.First(r, "WHERE id = 1")
if err != nil {
fmt.Println(err)
}
fmt.Println(r)
复制代码
输出如下: