背景
用golang对数据库标准操作进行封装,为后面的rest server提供数据库访问层。实现的目标是:能根据rest请求参数自动生成数据库操作语句,提供增、删、改、查、批量写入、事务等必要的数据库操作封装。并可以方便的扩展到多种数据库,让所有的数据库操作对于rest server来说表现为一致的访问接口。
一些关键点
- 接口设计做到恰到好处,够用且不繁杂。
- 函数参数的设计,Go不支持函数重载,如何善用interface{}。
- 用map[string]interface{}来处理rest的JSON请求参数,并自动生成相应的sql。
- 数据库查询结果能方便的转化为json,让rest server返回给用户。
代码解析
按功能模块对核心代码进行说明
IBock.go
数据库标准操作接口定义,根据我的实践经验,以下的接口设计已经能够很好的支持大部分的数据库操作,这些操作包括了根据json参数自动完成的CURD、手写sql支持、批量插入(更新)心及事务操作。
type IBock interface{
//根据参数,自动完成数据库查询
Retrieve(params map[string]interface{}, args ...interface{}) map[string]interface{}
//根据参数,自动完成数据库插入
Create(params map[string]interface{}, args ...interface{}) map[string]interface{}
//根据参数,自动完成数据库更新(只支持单条)
Update(params map[string]interface{}, args ...interface{}) map[string]interface{}
//根据参数,自动完成数据库删除(只支持单条)
Delete(params map[string]interface{}, args ...interface{}) map[string]interface{}
//手写查询sql支持
QuerySql(sql string, values []interface{}, params map[string]interface{}) map[string]interface{}
//手写非查询sql支持
ExecSql(sql string, values []interface{}) map[string]interface{}
//批量插入或更新
InsertBatch(tablename string, els []interface{}) map[string]interface{}
//事务支持
TransGo(objs map[string]interface{}) map[string]interface{}
}
参数说明
- params, 对应rest server接收到用户数据,由json对象转换而来。
- args,这个参数的目标是接收id(信息ID),fields(表字段数组),session(用户session)这三个参数,这样做的初衷是既要统一接口函数形式,又可以在编码时少传入作为点位符的nil
- values,为sql查询参数化提供的参数列表
- els,批量插入的每一行数据对象集
- objs,事务对象集
- 返回参数为go的映射,很容易转化为json。
Bock.go
接口的具体实现,本文是对Mysql的实现,暂只实现了基本的CURD,项目中会逐步完善。
//我们把操作对象定义在一个表上
type Bock struct {
Table string
}
//parseArgs函数的功能是解析args参数中包括的可变参数,实现在下面
func (b *Bock) Retrieve(params map[string]interface{}, args ...interface{}) map[string]interface{} {
//查询时我们一般只关注查询哪些表字段
_, fields, _ := parseArgs(args)
//调用具体的查询接口,查询接口将根据输入参数params自动实现sql查询语句,支持多样的查询定义,如:lks(从多个字体查询相同内容),ors(或查询),ins(in查询)等
return Query(b.Table, params, fields)
}
func (b *Bock) Create(params map[string]interface{}, args ...interface{}) map[string]interface{} {
//新建接口,一般都会关注用户在session的ID
_, _, session := parseArgs(args)
uId := session["userid"].(string)
params["u_id"] = uId
//调用具体的插入接口
return Insert(b.Table, params)
}
func (b *Bock) Update(params map[string]interface{}, args ...interface{}) map[string]interface{} {
//只支持单个更新,所以ID必须存在
id, _, _ := parseArgs(args)
if len(id) == 0 {
rs := make(map[string]interface{})
rs["code"] = 301
rs["err"] = "Id must be input."
return rs
}
return Update(b.Table, params)
}
func (b *Bock) Delete(params map[string]interface{}, args ...interface{}) map[string]interface{} {
//只支持单个删除,所以ID必须存在
id, _, _ := parseArgs(args)
if len(id) == 0 {
rs := make(map[string]interface{})
rs["code"] = 301
rs["err"] = "Id must be input."
return rs
}
return Delete(b.Table, params)
}
parseArgs函数的实现
func parseArgs(args []interface{}) (string, []string, map[string]interface{}) {
//解析指定的参数
var id string //信息ID
var fields []string //查询字段集
var session map[string]interface{} //用户session对象
for _, vs := range args {
switch vs.(type) {
case map[string]interface{}: //只接收指定类型
for k, v := range vs.(map[string]interface{}) {
if k == "id" {
id = v.(string)
}
if k == "fields" {
fields = v.([]string)
}
if k == "session" {
session = v.(map[string]interface{})
}
}
default:
}
}
return id, fields, session //返回解析成功的参数
}
Helper.go
数据操作的具体实现,大多是伪代码,项目后续会逐步完善,查询接口最重要,后面会有单独文章进行解析
func Query(tablename string, params map[string]interface{}, fields []string ) map[string]interface{} {
//调用具体实现的私用函数,接口中分自动和手动两个函数,在私用函数中屏蔽差异内聚功能
return query(tablename, params, fields, "", nil)
}
func Insert(tablename string, params map[string]interface{}) map[string]interface{} {
sql := "Insert into " + tablename
values := make([]interface{},0)
return execute(sql, values)
}
func Update(tablename string, params map[string]interface{}) map[string]interface{} {
sql := "Update " + tablename + " set "
values := make([]interface{},0)
return execute(sql, values)
}
func Delete(tablename string, params map[string]interface{}) map[string]interface{} {
sql := "Delete from " + tablename + " where"
values := make([]interface{},0)
return execute(sql, values)
}
私用查询函数定义
//五个输入参数,分别适配自动与手动查询
func query(tablename string, params map[string]interface{}, fields []string, sql string, vaules []interface{}) map[string]interface{} {
if vaules == nil {
vaules = make([]interface{},0)
}
//调用真正的数据库操作函数
return execQeury("select "+ strings.Join(fields, ",")+" from " + tablename, vaules)
}
非查询类具体操作函数
//因为golang把有结果集的和无结果集的操作是分开的,不象在java或node.js中,可以有高级函数进行统一操作,只能分开。
func execute(sql string, values []interface{}) map[string]interface{} {
//返回json对象,以map形式表达
rs := make(map[string]interface{})
rs["code"] = 200
return rs
}
查询类具体操作(已经实现),结果集以json对象封装,存储在map中
func execQeury(sql string, values []interface{}) map[string]interface{} {
var configs interface{}
...//省略数据配置获取代码,请参照以前的文章
dao, err := mysql.Open(dialect, dbUser + ":"+dbPass+"@tcp("+dbHost+":"+dbPort+")/"+dbName+"?charset="+dbCharset)
stmt, err := dao.Prepare(sql)
rows, err := stmt.Query(values...)
columns, err := rows.Columns() //取出字段名称
vs := make([]mysql.RawBytes, len(columns))
scans := make([]interface{}, len(columns))
for i := range vs { //预设取值地址
scans[i] = &vs[i]
}
var result []map[string]interface{}
for rows.Next() {
_ = rows.Scan(scans...) //塡入一列值
each := make(map[string]interface{})
for i, col := range vs {
if col != nil {
each[columns[i]] = string(col) //增值
}else{
each[columns[i]] = nil
}
}
result = append(result, each)
}
rs["code"] = 200
//data, _ := json.Marshal(result) //这样就能转换为json
rs["rows"] = result
return rs
}
数据库的批量操作,在前面的文章中已经用golang实现,只是还未封装,有兴趣的朋友可以看我前面的文章。
bock.go(程序入口)
最终目标的入口将是一个网络服务,提供标准的restful服务,现在只是用来测试,再这说明一下愿景。
table := Bock.Bock{ //上体实例
Table: "role", //对role表时行操作
}
var params map[string] interface{} //模拟json参数
args := make(map[string] interface{}) //其它参数
db := make([]DB.IBock, 1) //对接口编程
db[0] = &table //接口指向实例对象,这里可以现时处理多个不同的实例
fields := []string {"id", "name"}
args["fields"] = fields
rs, _ := db[0].Retrieve(params, args) //在这可以循环处理多个不同的实例,我们最终的目标就是在这接受用户的Http请求,由路由自动分发不同的请求,我们的数据库封装自动生成sql语句完成用户的基本需求。
fmt.Println(rs)
项目地址
https://GitHub.com/zhoutk/goTools
使用方法
git clone https://github.com/zhoutk/goTools
cd goTools
go get
go run bock.go
go buid bock.go
./bock
小结
经过多种方案的对比,发现go语言作为网络服务的吞吐率是最棒的,所以有了将以往在其它平台上的经验(node.js,java,python3),用go来实现,期望有惊喜,写代码我是认真的。
0