第二篇: golang 数据库增删改操作具体实现(mysql)
背景
这篇文章是 golang 针对数据库增删改 (非查询结果集, 查询语句的自动生成比较复杂, 下篇文章专门解析) 操作具体实现, 包括了自动生成 sql 与自定义 sql 相关函数, 以及指的插入与更新, 同时实现了异常处理.
一些关键点
利用 panic 与 recover 实现数据库异常处理.
函数可变参数的解析.
批量插入与更新使用同一个函数.
所有更新 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 来实现, 期望有惊喜, 写代码我是认真的.
来源: https://juejin.im/entry/5b3df476f265da0f4d0d566b