这是我参与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驱动,否则会报以下错误:
类比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)"`
}
复制代码
各参数提取方式如下:
- 字段名 => 反射获取标签中的json
- 类型 => 反射获取标签中的type
- 约束 => 反射获取标签中的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表,在数据库创建后,执行此代码,结果如下:
运行结果符合开始的预期。