《go 語言操作數據庫 CRUD》要點:
本文介紹了go 語言操作數據庫 CRUD,希望對您有用。如果有疑問,可以聯系我們。
go 語言標準庫已經提供數據庫拜訪通用接口,不同數據庫需搭配相應連接 Driver.標準庫里面 database/sql 實現基本數據類型 Scan, 基本的 Transaction 以及 sql 參數化.用這些東西去操作數據庫完全夠用,只是隨著項目代碼增長,sql string 會蔓延到項目的各個角落.若要修改數據庫表結構,這些 sql string 就是噩夢一般存在.流行做法是使用 ORM(Object Relational Mapping) 去解決這個問題.ORM 封裝好一些 CRUD 基本操作,可以避免大量手寫 sql string.
go 編譯型語言,無法做到像 Ruby 里面的 Method Missing 這樣動態特性,可以為一個復合類型的 struct 隨時添加一個 field. go 社區使用的 ORM 還是需要事先手動添加好 Field. 這樣做需要對數據庫和 struct 的成員做很多約定,寫好對應的拜訪 tag.這些仍然無法避免修改數據庫表時要一齊一齊修改 go 代碼.
我們可以仔細想想 ORM 真的是唯一的選擇嗎?ORM 實際上無法完美操作數據庫,它做的事情無非就是這些事情:
參數化 sql string.
封裝一些簡單的 CRUD 操作.
序列化 sql 查詢結果.
對于一些復雜的數據庫查詢語句,任何強大的 ORM 庫,靈活的動態語言特性都無法解決.況且 ORM 也未必是好東西,它簡化了操作,卻間接暗藏了數據庫的一些功能.如果不脫離 ORM 依賴我們無法去更好的操作數據庫,去自己優化查詢語句.以 go 語言目前的特性來看,這個社區永遠也不可能會做出能像動態語言那樣靈活的 ORM.關于 ORM 與 go 語言更詳細的吐槽請見這里.
如果不用 ORM 我們碰到的無非是上面提到的 3 個問題而已.把這個三個問題掰開處理,第一個問題是不存在的.參數化 sql string 這件事情我們只需要小心的處理就能防止 sql 注入這樣的平安問題.剩下的就是 2 和 3 的問題,而第二個問題我們暫時也不需要解決,我們一開始就去寫一些重復代碼好了.而問題 3 我們可以手動自己去序列化,也可以用社區的一些庫去解決這個問題.社區已經有了不錯的解決方案,我用的就是 sqlx 去解決的這個問題.
當初項目開始時我考察不少 ORM 庫后,果斷拋棄使用 ORM.項目經過一段時間迭代后,不用 ORM 沒有特別明顯的不便.唯一的問題就是我需要手動為每一張表做好 table 到 go struct 之間的映射.需要寫非常多重復性的 CRUD 操作代碼.這些代碼很難使用統一辦法減少重復,只好任其膨脹.直到有一天我看到這篇文章,原來 go 語言需要寫重復代碼這個問題在其他問題領域也存在,而社區老司機們解決這個問題的方式是使用機器生成代碼.社區大范圍這么做的原因是 go 語言的語法非常簡單,很容易用 go 生成 go 代碼.
寫 CRUD 生成器思路很簡單,只需要去數據庫里查詢 table 的詳細信息,為不同的 column 數據類型映射 go 數據類型.最后用 go 的 template 實現一些簡單的 CRUD 辦法.對于一般的 select 查詢語句我這里沒有去做實現,因為不同的表查詢條件是不一樣的,這種差異用函數傳參解決會更好.生成器做 column 映射和拼接查詢語句是極好的.
最后在項目里面把生成器生成的代碼和手寫的數據庫相關代碼分離開來,以方便數據庫表結構變動后可以讓生成器重新生成代碼.這里是我寫的這個簡單的生成器,參照了一部分 xo 項目的代碼,由于我只支持 Postgresql, 沒有去支持 join 和 has_many 這樣的操作,代碼相對他們要簡單很多.
事情到這里并沒有完,我們還需要序列化后的查詢結果做一些反序列化的工作,go 語言的 struct tag 對這方面有很好的支持.可是我們用機器生成的代碼是無法控制外界需要的各種反序列化的字段.下面以序列化 json 為例,我們并不想把數據庫的 ID 字段暴露給外界.如果用生成器去控制這些配置,生成器需要做更多的配置,數據庫表字段需要更多約定,這無疑會增加生成器的難度.我們必須要為每一張表手動單獨做一個 Export 的結構體與辦法,以滿足各種其他序列化的要求.
type UserExport struct {Name string `json:"name"`Email string `json:"email"`UpdatedAt string `json:"updated_at"`}func (u User) Export() UserExport {return UserExport{Name: u.Name,Email: u.Email,UpdatedAt: u.UpdatedAt,}}func (u User) MarshalJSON() ([]byte, error) {return json.Marshal(u.Export())}
如上所示,我們可以為 User 表配置不同 field 和 tag.可以單獨為不同的序列化數據類型寫一個 Marshal 辦法,這些都是手動可以控制.為了靈活我們還可以把 Export 這個結構體直接嵌入到其他結構體中,不用擔心 Marshal 會繼承式覆蓋后面 Marshal 辦法.
維易PHP培訓學院每天發布《go 語言操作數據庫 CRUD》等實戰技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養人才。