第二篇: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/goToolscd goToolsgo getgo run bock.gogo buid bock.go./bock
小結
經過多種方案的對比,發現go語言作為網路服務的吞吐率是最棒的,所以有了將以往在其它平台上的經驗(node.js,java,python3),用go來實現,期望有驚喜,寫代碼我是認真的。