第二篇: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来实现,期望有惊喜,写代码我是认真的。