golang实现rest server框架(二)

第二篇:golang数据库增删改操作具体实现(mysql)

背景

这篇文章是golang针对数据库增删改(非查询结果集,查询语句的自动生成比较复杂,下篇文章专门解析)操作具体实现,包括了自动生成sql与自定义sql相关函数,以及指的插入与更新,同时实现了异常处理。

一些关键点

  1. 利用panic与recover实现数据库异常处理。
  2. 函数可变参数的解析。
  3. 批量插入与更新使用同一个函数。
  4. 所有更新sql语句参数化。

代码解析

按功能模块对核心代码进行说明

异常处理

golang语言没有异常处理,但可以通过panic、recover及defer来实现,值得注意的一点是,如何在defer中返回相应的信息给上层函数。

                                              //rs要在这定义,defer中修改rs的信息才能返回到上层调用函数
func execute(sql string, values []interface{}) (rs map[string]interface{}) {
    log.Println(sql, values)
    rs = make(map[string]interface{})        //我原本rs是在这声明并定义的,结果返回为空
    defer func() {
        if r := recover(); r != nil {
            rs["code"] = 500                //仔细想来,两个返回路径,一个是正常return,一个是声明中的rs返回值
            rs["err"] = "Exception, " + r.(error).Error()
        }
    }()
    ...
    //这其中的代码若引发了panic,在返回上层调用函数前会执行defer
    ...
    return rs
}

非查询操作的底层封装函数(execute)

golang的数据库操作分返回查询结果集的和无查询结果集的,没找到能统一处理的API,象java、node.js一样,我只能分开封装了,这里实现execute。

func execute(sql string, values []interface{}) (rs map[string]interface{}) {
    log.Println(sql, values)
    ...
    //异常处理与数据配置文件读取
    ...
    //连接数据库
    dao, err := mysql.Open(dialect, dbUser+":"+dbPass+"@tcp("+dbHost+":"+dbPort+")/"+dbName+"?charset="+dbCharset)
    stmt, _ := dao.Prepare(sql)        //预处理
    ers, err := stmt.Exec(values...)   //提供参数并执行
    if err != nil {
        rs["code"] = 204                //错误处理
        rs["err"] = err.Error()
    } else {
        id, _ := ers.LastInsertId()        //自动增长ID的最新值,若插入
        affect, _ := ers.RowsAffected()    //影响的行数
        rs["code"] = 200
        rs["info"] = sql[0:6] + " operation success."
        rs["LastInsertId"] = id
        rs["RowsAffected"] = affect

    }
    return rs                         //成功返回
}

参数说明:

  • sql,调用这个函数时,要么已经自动生成了标准的sql,要么就自定义的sql,所有的语句要求是参数化形式的
  • values,参数列表,与sql中的占位符一一对应

新增函数的实现(Insert)

数据新增操作的具体实现,这个是根据用户提交的json数据自动生成标准sql的函数。

func Insert(tablename string, params map[string]interface{}) map[string]interface{} {
    values := make([]interface{}, 0)
    sql := "INSERT INTO `" + tablename + "` (" //+strings.Join(allFields, ",")+") VALUES ("
    var ks []string
    var vs []string
    for k, v := range params {            //注意:golang中对象的遍历,字段的排列是随机的
        ks = append(ks, "`" + k + "`")    //保存所有字段
        vs = append(vs, "?")              //提供相应的占位符
        values = append(values, v)        //对应保存相应的值
    }
    //生成正常的插入语句
    sql += strings.Join(ks, ",") + ") VALUES (" + strings.Join(vs, ",") + ")"
    return execute(sql, values)
}

修改函数的实现(Update)

数据修改操作的具体实现,这个是根据用户提交的json数据自动生成标准sql的函数。

func Update(tablename string, params map[string]interface{}, id string) map[string]interface{} {
    values := make([]interface{}, 0)
    sql := "UPDATE `" + tablename + "` set " //+strings.Join(allFields, ",")+") VALUES ("
    var ks string
    index := 0
    psLen := len(params)
    for k, v := range params {        //遍历对象
        index++
        values = append(values, v)    //参数
        ks += "`" + k + "` =  ?"      //修改一个key的语句
        if index < psLen {            //非最后一个key,加逗号
            ks += ","
        }
    }
    values = append(values, id)      //主键ID是单独的
    sql += ks + " WHERE id = ? "
    return execute(sql, values)
}

删除函数的实现(Delete)

数据删除操作的具体实现。

func Delete(tablename string, id string) map[string]interface{} {
    sql := "DELETE FROM " + tablename + " where id = ? "        //只支持单个ID操作,这是自动化的接口,批量操作走其它接口
    values := make([]interface{}, 0)
    values = append(values, id)
    return execute(sql, values)
}

批量新增与修改函数的实现(InsertBatch)

数据批量新增与修改操作的具体实现,两种方式在同一接口中实现。

func InsertBatch(tablename string, els []map[string]interface{}) map[string]interface{}  {
    values := make([]interface{}, 0)
    sql := "INSERT INTO " + tablename
    var upStr string
    var firstEl map[string]interface{}        //第一个插入或修改的对象
    lenEls := len(els)                        //因为golang对象遍历的随机性,我们取出第一个对象先分析,去除随机性
    if lenEls > 0 {
        firstEl = els[0]
    }else {                                  //一个元素都没有,显然调用参数不对
        rs := make(map[string]interface{})
        rs["code"] = 301
        rs["err"] = "Params is wrong, element must not be empty."
        return rs
    }
    var allKey []string                      //保存一个对象的所有字段,对象访问时就按这个顺序
    eleHolder := "("
    index := 0
    psLen := len(firstEl)
    for k, v := range firstEl {
        index++
        eleHolder += "?"                          //占位符
        upStr += k + " = values (" + k + ")"      //更新操作时的字段与值对应关系
        if index < psLen {                        //非最后一个key
            eleHolder += ","
            upStr += ","
        }else{
            eleHolder += ")"
        }
        allKey = append(allKey, k)               //key
        values = append(values, v)               //value
    }
    //批量操作的第一个对象语句的自动生成
    sql += " ("+strings.Join(allKey, ",")+") values " + eleHolder

    for i := 1; i < lenEls; i++ {             //依据对第一个对象的分析,生成所有的后续对象
        sql += "," + eleHolder
        for _, key := range allKey {
            values = append(values, els[i][key])
        }
    }
    //当主键或唯一索引存在时,进行更新操作的sql语句生成
    sql += " ON DUPLICATE KEY UPDATE " + upStr
    return execute(sql, values)
}

bock.go(程序入口)

这里提供一些用于测试的代码。

用于测试的数据表结构

CREATE TABLE `books` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '唯一性索引',
  `name` varchar(64) DEFAULT '' COMMENT '名称',
  `isbn` varchar(64) DEFAULT '' COMMENT '图书ISBN',
  `u_id` int(11) DEFAULT '0' COMMENT '用户ID',
  `status` tinyint(4) DEFAULT '1' COMMENT '状态:0-禁;1-有效;9删除',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uuid` (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='表';

新增测试

    params := make(map[string] interface{})
    args := make(map[string] interface{})
    session := make(map[string] interface{})
    session["userid"] = "112"
    args["session"] = session
    params["name"] = "golang实战"
    params["isbn"] = "41563qtrs5-X"
    params["status"] = 1
    db := &table
    rs := db.Create(params, args)
    fmt.Println(rs)

修改测试

    params = make(map[string] interface{})
    args = make(map[string] interface{})
    args["id"] = 2
    params["name"] = "golang实战,修改了"
    params["status"] = 3
    rs = db.Update(params, args)
    fmt.Println(rs)

删除测试

    args = make(map[string] interface{})
    args["id"] = 1
    rs = db.Delete(nil, args)
    fmt.Println(rs)

批量测试

    vs := make([]map[string]interface{}, 0)

    params := make(map[string] interface{})
    params["name"] = "golang批量11213"        //第一个对象
    params["isbn"] = "4156s5"
    params["status"] = 5
    params["id"] = 9
    vs = append(vs, params)

    params = make(map[string] interface{})
    params["name"] = "golang批量22af24"        //第二个对象
    params["isbn"] = "xxfqwt325rqrf45"
    params["status"] = 2
    params["id"] = 10
    vs = append(vs, params)

    db := &table
    rs := db.InsertBatch("books", vs)
    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
    原文地址: https://segmentfault.com/a/1190000015484267
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞