13

第一篇:用golang对数据库标准操作进行封装(mysql)

背景

用golang对数据库标准操作进行封装,为后面的rest server提供数据库访问层。实现的目标是:能根据rest请求参数自动生成数据库操作语句,提供增、删、改、查、批量写入、事务等必要的数据库操作封装。并可以方便的扩展到多种数据库,让所有的数据库操作对于rest server来说表现为一致的访问接口。

一些关键点

  1. 接口设计做到恰到好处,够用且不繁杂。
  2. 函数参数的设计,go不支持函数重载,如何善用interface{}。
  3. 用map[string]interface{}来处理rest的json请求参数,并自动生成相应的sql。
  4. 数据库查询结果能方便的转化为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来实现,期望有惊喜,写代码我是认真的。


zhoutk
2.6k 声望1.2k 粉丝

自由程序员,技术路线c,delphi,c++,c#,java,php,node.js,python,golang,typescript;超喜欢react.js的设计思路,全栈开发成为我的终极目标。开发机器macbook pro,或装ubuntu、fedora的机器,编程用vim...