G

[Golang] 反射实现ORM的Find方法

RoLingG Golang 2025-05-22

ORM的Find方法实现

最近复习了一下 Go 的反射,反射在很多 Go 语言的框架都有用到,有些场景下使用起来会十分的便利,最近看了一下就学着一些文档小搓了一个 ORM 的 Find 方法,主要需要做的事情就是将 ? 替换成需要写入的结构体内的元素,以及拼凑出对应的 sql 语句。

代码如下:

package main

import (
    "errors"
    "fmt"
    "reflect"
    "strconv"
    "strings"
)

type Student struct {
    Name string `rg-orm:"name"`
    Age  int    `rg-orm:"age"`
}

type UserInfo struct {
    Id   int    `rg-orm:"id"`
    Name string `rg-orm:"name"`
    Age  int    `rg-orm:"age"`
}

func Find(obj any, query ...any) (sql string, err error) {
    // 鉴定入参类型,看是不是表映射的结构体
    objType := reflect.TypeOf(obj)
    if objType.Kind() != reflect.Struct {
        err = errors.New("非结构体,入参类型错误")
        return "", err
    }

    var where string
    // 判断是否有查询条件元素
    if len(query) > 0 {
        // 有的话则先切割出where语句
        q := query[0]
        // 鉴定where语句中有多少个需要映射进去的参数
        if strings.Count(q.(string), "?")+1 > len(query) {
            if err != nil {
                err = errors.New("参数不足")
                return "", err
            }
        }
        fmt.Println(q.(string), 0)
        // 检测映射的入参元素类型,将其转换成对应类型,方便后续sql语句拼接
        for _, value := range query[1:] {
            valueType := reflect.TypeOf(value)
            switch valueType.Kind() {
            case reflect.String:
                q = strings.Replace(q.(string), "?", value.(string), 1)
                fmt.Println(q, 1)
            case reflect.Int:
                q = strings.Replace(q.(string), "?", strconv.Itoa(value.(int)), 1)
                fmt.Println(q, 2)
            }
        }
        where = "where " + q.(string)
        fmt.Println(where, 3)
    }

    // 获取表内所有Tag标记出来的字段
    var columns []string
    for i := 0; i < objType.NumField(); i++ {
        // 获取表映射结构体的字段
        field := objType.Field(i)
        fmt.Println(field, 4)
        // 获取标签的值,也就是对应sql表所需要查询的字段,一般orm都是通过tag来映射结构体中一个元素对应表的字段名
        f := field.Tag.Get("rg-orm")
        fmt.Println(f, 5)
        columns = append(columns, f)
    }

    // 获取表名,一般表名为对应结构体的小写+复数的形式,反正gorm是这样的
    sqlTable := strings.ToLower(objType.Name()) + "s"

    // 拼接sql语句
    sql = fmt.Sprintf("SELECT %s FROM %s %s", strings.Join(columns, ","), sqlTable, where)
    return sql, err
}

func main() {
    sql, err := Find(Student{}, "name = ? and age = ?", "rolingg", 23)
    fmt.Println(sql, err, 6) // select name,age from students where name = 'rolingg' and age = 23
    sql, err = Find(UserInfo{}, "id = ?", 1)
    fmt.Println(sql, err, 7) // select id,name,age from userinfos where id = 1
}

输出为:

name = ? and age = ? 0
name = rolingg and age = ? 1
name = rolingg and age = 23 2
where name = rolingg and age = 23 3
{Name  string rg-orm:"name" 0 [0] false} 4
name 5
{Age  int rg-orm:"age" 16 [1] false} 4
age 5
SELECT name,age FROM students where name = rolingg and age = 23 <nil> 6
id = ? 0
id = 1 2
where id = 1 3
{Id  int rg-orm:"id" 0 [0] false} 4
id 5
{Name  string rg-orm:"name" 8 [1] false} 4
name 5
{Age  int rg-orm:"age" 24 [2] false} 4
age 5
SELECT id,name,age FROM userinfos where id = 1 <nil> 7

关于表名那里,有时候用的是驼峰或者用 _ 进行分割的写法,不同项目写法都有可能不同,改变一下就可以了。

突然想要实现以下这种写法也是因为,Gorm 框架有时候可能会不使用一些数据库的语法,不同数据库的一些语法会有差别,像之前我在实习的时候,Gorm 就不适配海量数据库,需要开发人员去进行驱动的魔改和修正语句使用,还有一些适配的语句可能适用于mysqlpgsql,但换到一些小众数据库为了作区分而改变了部分语法的,就会出一定的问题,可能就需要魔改一些 ORM 语句的逻辑判断和写法。

以上,就这样,随便写写。

PREV
[Golang基础] 反射

评论(0)

发布评论

登录身份: RoLingG. 退出 »