[Golang]OpenGauss数据库简易orm搭建(一)自动建表

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

前言

之前,我们使用Python实现了对OpenGauss的连接操作,但由于Go的反射比较好使,因此这次换用Go语言连接OpenGauss,并构建一个简单的orm。

连接数据库

支持Postgresql的驱动一般都是支持OpenGauss的,这里我选择的是bmizeran/pq作为驱动。

首先,我们需要先构建配置文件:

# app.ini
[database]
User = jack
Password = ********
Host = **.**.**.**
Port = 26000
Name = postgres
复制代码

然后构建一个Setup函数,将配置文件进行配置,然后使用sql.open()实现连接操作:

// models.go
package models

import (
   "database/sql"
   "fmt"
   _ "github.com/bmizerany/pq"
   "log"
   "main/pkg/setting"
)

var db *sql.DB

// Setup 初始化数据库
func Setup() {
   var err error
   dsn := fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=disable TimeZone=Asia/Shanghai",
         setting.DatabaseSetting.Host,
         setting.DatabaseSetting.Port,
         setting.DatabaseSetting.User,
         setting.DatabaseSetting.Name,
         setting.DatabaseSetting.Password,
      )
   db, err = sql.Open("postgres", dsn)
   if err != nil {
      log.Fatalf("数据库配置错误: %v", err)
   }
}
复制代码

这里需要注意,虽然看起来好像没有使用到bmizerany/pg,但是我们仍需要进行导入(需要加个_),导入后会在后台引入一个postgres驱动,否则会报以下错误:

07$KK__WRX7H3KT}3{3FO.png

类比Python库,两者的关系类似于BeautifulSoup和lxml的关系,这里就不展开了。

自动建表

这里先展示一段使用sql语句实现的建表操作:

CREATE TABLE Account (
   id serial PRIMARY KEY,
   name VARCHAR(64) NOT NULL,
   username VARCHAR(64) NOT NULL,
   password VARCHAR(64) NOT NULL,
   role_id INT NOT NULL,
   FOREIGN KEY(role_id) REFERENCES Role(id)
);
复制代码

这里我们一共出现了5条字段和一个额外行与role表用于建立关联。

5条字段我们不难发现创建结构是相同的: 字段名 类型 约束

因此需要实现自动建表,实践上是想办法从结构体struct中提取出对应部分,想要实现这种功能,理所当然想到使用反射和标签进行实现。

下面是一种比较方便编码实现的方案:

type Account struct {
   Id       string `json:"id" type:"serial" constraint:"PRIMARY KEY"`
   Name     string `json:"name" type:"VARCHAR(64)" constraint:"NOT NULL"`
   Username string `json:"account" type:"VARCHAR(64)" constraint:"NOT NULL"`
   Password string `json:"password" type:"VARCHAR(64)" constraint:"NOT NULL"`
   RoleId   string `json:"role_id" type:"INT" constraint:"NOT NULL"`

   extra string `constraint:"FOREIGN KEY(role_id) REFERENCES Role(id)"`
}
复制代码

各参数提取方式如下:

  1. 字段名 => 反射获取标签中的json
  2. 类型 => 反射获取标签中的type
  3. 约束 => 反射获取标签中的constraint

然后使用extra的constraint保存其他出现的关联、约束条件。

这里也有很多不合理的地方,例如:字段名可以通过反射Field.Name获取,数据类型可以通过Field的数据类型实现自动设置,关联等条件也可以通过Tag自动生成等。但是会增加较大编码量,等后面再优化吧。

上述功能的代码实现大体如下,主要通过字符串拼接的方式生成sql语句,然后交付数据库处理:

// CreateTable 创建表
func CreateTable(tables []interface{})  {
   for _, table := range tables {
      t := reflect.TypeOf(table)
      tableName := strings.Split(t.String(), ".")[1]
      sql := "CREATE TABLE " + tableName + " (\n"
      for i := 0; i < t.Elem().NumField(); i++ {
         field := t.Elem().Field(i)
         if i == t.Elem().NumField() - 1 {
            extra := field.Tag.Get("constraint")
            if extra != "" {
               sql += ",\n\t" + extra
            }
            break
         }
         if i != 0 {
            sql += ",\n\t"
         }
         sql += fmt.Sprintf(
            "%s %s %s",
            field.Tag.Get("json"),
            field.Tag.Get("type"),
            field.Tag.Get("constraint"),
         )
      }

      sql += "\n);"
      logrus.Debugln(sql)
      _, err := db.Exec(sql)
      if err != nil {
         logrus.Errorln(tableName, "创建失败:", err.Error())
      } else {
         logrus.Println(tableName, "创建成功")
      }

   }
}
复制代码

其中,出现了一个循环,通过反射的方式,获取我们编写的结构体的各field:

t := reflect.TypeOf(table)
for i := 0; i < t.Elem().NumField(); i++ {
    field := t.Elem().Field(i)
}
复制代码

获取到了每个field,实现标签读取就简单了:

name := field.Tag.Get("json")
dType := field.Tag.Get("type")
constraint := field.Tag.Get("constraint")
复制代码

使用方法

CreateTable([]interface{}{
   &Role{},
   &Account{},
})
复制代码

由于存在外键关系,我们需要先传教Role表,然后构建Account表,在数据库创建后,执行此代码,结果如下:

MI72D.png

LG.png

运行结果符合开始的预期。

猜你喜欢

转载自juejin.im/post/7035267386081017863