網站首頁 編程語言 正文
前言
go項目中經常需要查詢db,按照以前java開發經驗,會根據查詢條件寫很多方法,如:
- GetUserByUserID
- GetUsersByName
- GetUsersByAge
每一種查詢條件寫一個方法,這種方式對外是挺好的,對外遵循嚴格原則,讓每個對外的方法接口是明確的。但是對內的話,應該盡可能的通用,做到代碼復用,少寫代碼,讓代碼看起來更優雅、整潔。
問題
在review代碼的時候,針對上面3個方法,一般寫法是
func GetUserByUserID(ctx context.Context, userID int64) (*User, error){ db := GetDB(ctx) var user User if userID > 0 { db = db.Where(`userID = ?`, userID) } if err := db.Model(&User{}).Find(&user).Err; err != nil { return nil, err } return user, nil } func GetUsersByName(ctx context.Context, name string) (*User, error){ db := GetDB(ctx) var users []User if name != "" { db = db.Where(`name like '%%'`, name) } if err := db.Model(&User{}).Find(&users).Err; err != nil { return nil, err } return users, nil } func GetUsersByAge(ctx context.Context, age int64) (*User, error){ db := GetDB(ctx) var user User if age > 0 { db = db.Where(`age = ?`, age) } if err := db.Model(&User{}).Find(&user).Err; err != nil { return nil, err } return user, nil }
當User表上字段有幾十個的時候,上面類似的方法會越來越多,代碼沒有做到復用。當有Teacher表、Class表等其他表的時候,上面的查詢方法又要翻倍。
調用方也會寫的很死,參數固定。當要增加一個查詢條件的時候,要么改原來的函數,增加一個參數,這樣其他調用的地方也都要改;要么新寫一個函數,這樣函數越來越多,難以維護和閱讀。
上面是青銅寫法,針對這種情況,下面介紹幾種白銀、黃金、王者寫法
白銀
將入參定義成一個結構體
type UserParam struct { ID int64 Name string Age int64 }
將入參都放在UserParam結構體中
func GetUserInfo(ctx context.Context, info *UserParam) ([]*User, error) { db := GetDB(ctx) db = db.Model(&User{}) var infos []*User if info.ID > 0 { db = db.Where("user_id = ?", info.ID) } if info.Name != "" { db = db.Where("user_name = ?", info.Name) } if info.Age > 0 { db = db.Where("age = ?", info.Age) } if err := db.Find(&infos).Err; err != nil { return nil, err } return infos, nil }
這個代碼寫到這里,相比最開始的方法其實已經好了不少,至少 dao 層的方法從很多個入參變成了一個,調用方的代碼也可以根據自己的需要構建參數,不需要很多空占位符。但是存在的問題也比較明顯:仍然有很多判空不說,還引入了一個多余的結構體。如果我們就到此結束的話,多少有點遺憾。
另外,如果我們再擴展一下業務場景,我們使用的不是等值查詢,而是多值查詢或者區間查詢,比如查詢 status in (a, b),那上面的代碼又怎么擴展呢?是不是又要引入一個方法,方法繁瑣暫且不說,方法名叫啥都會讓我們糾結很久;或許可以嘗試把每個參數都從單值擴展成數組,然后賦值的地方從 = 改為 in()的方式,所有參數查詢都使用 in 顯然對性能不是那么友好。
黃金
更高級的優化方法,是使用高階函數。
type Option func(*gorm.DB)
定義 Option 是一個函數,這個函數的入參類型是*gorm.DB,返回值為空。
然后針對每一個需要查詢的字段,定義一個高階函數
func UserID(ID int64) Option { return func(db *gorm.DB) { db.Where("`id` = ?", ID) } } func Name(name int64) Option { return func(db *gorm.DB) { db.Where("`name` like %?%", name) } } func Age(age int64) Option { return func(db *gorm.DB) { db.Where("`age` = ?", age) } }
返回值是Option類型。
這樣上面3個方法就可以合并成一個方法了
func GetUsersByCondition(ctx context.Context, opts ...Option)([]*User, error) { db := GetDB(ctx) for i:=range opts { opts[i](db) } var users []User if err := db.Model(&User{}).Find(&users).Err; err != nil { return nil, err } return users, nil }
沒有對比就沒有傷害,通過和最開始的方法比較,可以看到方法的入參由多個不同類型的參數變成了一組相同類型的函數,因此在處理這些參數的時候,也無需一個一個的判空,而是直接使用一個 for 循環就搞定,相比之前已經簡潔了很多。
還可以擴展其他查詢條件,比如IN,大于等
func UserIDs(IDs int64) Option { return func(db *gorm.DB) { db.Where("`id` in (?)", IDs) } } func AgeGT(age int64) Option { return func(db *gorm.DB) { db.Where("`age` > ?", age) } }
而且這個查詢條件最終是轉換成Where條件,跟具體的表無關,也就是說這些定義是可以被其他表復用的。
王者
優化到上述方法已經可以了,但是王者一般會繼續優化。
上述方法GetUsersByCondition只能查User表,能不能更通用一些,查任意表呢?分享GetUsersByCondition方法,發現如果要做到查任意表,有2個阻礙:
- 表明是在方法中寫死的
- 返回值定義的是[]*User,不能通用
針對第一個問題,我們可以定義一個Option來實現
func TableName(tableName string) Option { return func(db *grom.DB) { db.Table(tableName) } }
針對第二個問題,可以將返回參數作為入參,通過引用的方式傳進來
func GetRecords(ctx context.Context, in any, opts ...Option) { db := GetDB(ctx) for i:=range opts { opts[i](db) } return db.Find(in).Err } // 調用:根據user name 和age 查詢users var users []User if err := GetRecords(ctx, &users, TableName("user"), Name("張三"), Age(18)); err != nil { // TODO }
總結
這里通過對 grom 查詢條件的抽象,大大簡化了對 DB 組合查詢的寫法,提升了代碼的簡潔。
原文鏈接:https://juejin.cn/post/7184804445991993403
相關推薦
- 2022-09-05 淺談異常分類及異常處理機制
- 2022-02-17 MacOS Catalina啟用三指拖移
- 2022-03-06 C語言動態順序表實例代碼_C 語言
- 2022-01-05 實體類[notmapped]特殊 “The specified type member ‘‘ is
- 2022-06-22 一文搞懂C++多態的用法_C 語言
- 2022-08-04 C++中純虛函數的實例詳解_C 語言
- 2022-01-20 關于 Symbol() 能不能當作 key值使用
- 2022-01-18 VSCode git拉取代碼,提示:在簽出前,請清理存儲庫工作樹。
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支