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