第一篇: 用 golang 对数据库标准操作进行封装(mysql)
背景
用 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 来实现, 期望有惊喜, 写代码我是认真的.
来源: https://juejin.im/entry/5b3c4781f265da0f6825a8b6