往期熱門(mén)文章:
1、別再重復(fù)造輪子了,一個(gè) Spring 注解輕松搞定循環(huán)重試功能!
(資料圖片)
2、2023最新互聯(lián)網(wǎng)公司時(shí)長(zhǎng)排行榜出爐!
3、棄用 Nginx 后!它成為了最受歡迎 Web 服務(wù)器。。。
4、計(jì)算機(jī)會(huì)成為下一個(gè)土木嗎?
5、干掉Maven和Gradle!推出更強(qiáng)更快更牛逼的新一代構(gòu)建工具,炸裂!
Bean Searcher 號(hào)稱 任何復(fù)雜的查詢都可以 一行代碼搞定,但 Mybatis Plus 似乎也有類似的動(dòng)態(tài)查詢功能,它們有怎樣的區(qū)別呢?
Mybatis Plus 依賴 MyBatis, 功能 CRUD 都有,而 Bean Seracher 不依賴任何 ORM,只專注高級(jí)查詢。
只有使用 MyBatis 的項(xiàng)目才會(huì)用 Mybatis Plus,而使用 Hibernate,Data Jdbc 等其它 ORM 的人則無(wú)法使用 Mybatis Plus。但是這些項(xiàng)目都可以使用 Bean Searcher(可與任何 ORM 配合使用,也可單獨(dú)使用)。
使用 Mybatis Plus 需要編寫(xiě)實(shí)體類 和 Mapper 接口,而 Bean Searcher 只需編寫(xiě) 實(shí)體類,無(wú)需編寫(xiě)任何接口。
這個(gè)區(qū)別意義其實(shí)不大,因?yàn)槿绻阌?Mybatis Plus,在增刪改的時(shí)候還是需要定義 Mapper 接口。
Mybatis Plus 的 字段運(yùn)算符 是靜態(tài)的,而 Bean Searcher 的是動(dòng)態(tài)的。
字段運(yùn)算符指的是某字段參與條件時(shí)用的是=、>亦或是like這些條件類型。不只 Mybatis Plus,一般的傳統(tǒng) ORM 的字段運(yùn)算符都是靜態(tài)的,包括 Hibernate、Spring data jdbc、JOOQ 等。
下面舉例說(shuō)明。對(duì)于只有三個(gè)字段的簡(jiǎn)單實(shí)體類:
publicclassUser{ privatelongid; privateString name; privateintage; // 省略 Getter Setter}
依賴:
implementation "com.baomidou:mybatis-plus-boot-starter:3.5.2
首先要寫(xiě)一個(gè) Mapper 接口:
publicinterfaceUserMapperextendsBaseMapper
然后在 Controller 里寫(xiě)查詢接口:
@RestController@RequestMapping("/user")publicclassUserController{ @AutowiredprivateUserMapper userMapper; @GetMapping("/mp")publicList
此時(shí)這個(gè)接口可以支持 三個(gè)檢索參數(shù),id, name, age,例如:
GET /user/mp? name=Jack查詢 name等于Jack 的數(shù)據(jù)
GET /user/mp? age=20查詢 age等于20 的數(shù)據(jù)
但是他們所能表達(dá)的關(guān)系都是等于,如果你還想查詢age > 20的數(shù)據(jù),則無(wú)能為力了,除非在實(shí)體類的 age 字段上加上一條注解:
@TableField(condition = "%s>#{%s}")privateintage;
但加了注解后,age 就只能表達(dá)大于的關(guān)系了,不再可以表達(dá) 等于了。所以說(shuō),MyBatit Plus 的字段運(yùn)算符是靜態(tài)的,不能由參數(shù)動(dòng)態(tài)指定。
當(dāng)然我們可以在 Controller 里根據(jù)參數(shù)調(diào)用 QueryWrapper 的不同方法讓它支持,但這樣代碼就不只一行了,檢索的需求越復(fù)雜,需要編寫(xiě)的代碼就越多了。
依賴:
implementation "cn.zhxu:bean-searcher-boot-starter:4.1.2"
不用編寫(xiě)任何接口,復(fù)用同一個(gè)實(shí)體類,直接進(jìn)行查詢:
@RestController@RequestMapping("/user")publicclassUserController{ @AutowiredprivateBeanSearcher beanSearcher; @GetMapping("/bs")publicList
此時(shí)這個(gè)接口可以支持的檢索參數(shù)就非常多了:
GET /user/bs? name=Jack查詢 name等于Jack 的數(shù)據(jù)
GET /user/bs? name=Jack & name-ic=true查詢 name等于Jack 時(shí)忽略大小寫(xiě)
GET /user/bs? name=Jack & name-op=ct查詢 name包含Jack 的數(shù)據(jù)
GET /user/bs? age=20查詢 age等于20 的數(shù)據(jù)
GET /user/bs? age=20 & age-op=gt查詢 age大于20 的數(shù)據(jù)
等等...
可以看出,Bean Searcher對(duì)每個(gè)字段使用的運(yùn)算符都可以由參數(shù)指定,它們是動(dòng)態(tài)的。
無(wú)論查詢需求簡(jiǎn)單還是復(fù)雜,Controller 里都只需一行代碼。參數(shù)xxx-op可以傳哪些值?參閱這里:bs.zhxu.cn/guide/lates…
看到這里,如果看的明白,應(yīng)該有一半的讀者開(kāi)始感慨:好家伙,這不是把后端組裝查詢條件的過(guò)程都甩給了前端?誰(shuí)用了這個(gè)框架,不會(huì)被前端打死嗎?
哈哈,我是不是道出了你現(xiàn)在心里的想法?如果你真的如此想,請(qǐng)仔細(xì)回看我們正在討論的主題:【高級(jí)查詢】! 如果不能理解什么是高級(jí)查詢,我再貼個(gè)圖助你思考:
當(dāng)然也并不是所有的檢索需求都如此復(fù)雜,當(dāng)前端不需要控制檢索方式時(shí),xxx-op參數(shù) 可以省略,省略時(shí),默認(rèn)表達(dá)的是等于,如果你想表達(dá) 其它方式,只需一個(gè)注解即可,例如:
@DbField(onlyOn = GreaterThan.class)privateintage;
這時(shí),當(dāng)前端只傳一個(gè)age參數(shù)時(shí),執(zhí)行的 SQL 條件就是age > ?了,并且即使前端多傳一個(gè)age-op參數(shù),也不再起作用了。
這其實(shí)是條件約束,下文會(huì)繼續(xù)講到。
就上文所例的代碼,除卻運(yùn)算符 動(dòng)靜 的區(qū)別,Mybatis Plus 對(duì)接收到的參數(shù)生成的條件 都是且的關(guān)系,而 Bean Searcher 默認(rèn)也是且,但支持 邏輯分組。
再舉例說(shuō)明,假設(shè)查詢條件為:
(name = Jack 并且 age = 20)或者(age = 30)
此時(shí),MyBatis Plus 的一行代碼就無(wú)能為力了,但 Bean Searcher 的一行代碼仍然管用,只需這樣傳參即可:
GET /user/bs? a.name=Jack & a.age=20 & b.age=30 & gexpr=a|b
這里 Bean Searcher 將參數(shù)分為a,b兩組,并用新參數(shù)gexpr來(lái)表達(dá)這兩組之間的關(guān)系(a或b)。
實(shí)際傳參時(shí)gexpr的值需要URLEncode編碼一下: URLEncode("a|b") => "a%7Cb",因?yàn)?HTTP 規(guī)定參數(shù)在 URL 上不可以出現(xiàn)|這種特殊字符。當(dāng)然如果你喜歡 POST, 可以將它放在報(bào)文體里。
什么場(chǎng)景下適合使用此功能?當(dāng)遇見(jiàn)類似下圖中的需求時(shí),它將助你一招制敵:
分組功能非常強(qiáng)大,但如此復(fù)雜的檢索需求也確實(shí)罕見(jiàn),這里不再細(xì)述,詳情可閱:bs.zhxu.cn/guide/lates…
在不寫(xiě) SQL 的情況下,Mybatis Plus 的動(dòng)態(tài)查詢 僅限于 單表,而 Bean Searcher 單表 和 多表 都支持的一樣好。
這也是很重要的一點(diǎn)區(qū)別,因?yàn)?strong>大多數(shù)高級(jí)查詢場(chǎng)景都是需要聯(lián)表的。
當(dāng)然有些人堅(jiān)持用單表做查詢,為了避免聯(lián)表,從而在主表中冗余了很多字段,這不僅造成了 數(shù)據(jù)庫(kù)存儲(chǔ)空間壓力急劇增加,還讓項(xiàng)目更加難以維護(hù)。因?yàn)樵磾?shù)據(jù)一但變化,你必須同時(shí)更新這些冗余的字段,只要漏了一處,BUG 就跳出來(lái)了。
還是舉個(gè)例子,某訂單列表需要展示 訂單號(hào),訂單金額,店鋪名,買家名 等信息,用 Bean Searcher 實(shí)體類可以這么寫(xiě):
@SearchBean(tables = "order o, shop s, user u", // 三表關(guān)聯(lián) where = "o.shop_id = s.id and o.buyer_id = u.id", // 關(guān)聯(lián)關(guān)系 autoMapTo = "o" // 未被 @DbField 注解的字段都映射到 order 表)publicclassOrderVO{ privatelongid; // 訂單ID o.id privateString orderNo; // 訂單號(hào) o.order_no privatelongamount; // 訂單金額 o.amount @DbField("s.name")privateString shop; // 店鋪名 s.name @DbField("u.name")privateString buyer; // 買家名 u.name // 省略 Getter Setter}
有心的同學(xué)會(huì)注意到,這個(gè)實(shí)體類的命名并不是 Order, 而是 OrderVO。這里只是一個(gè)建議的命名,因?yàn)樗潜举|(zhì)上就是一個(gè) VO(View Object),作用只是一個(gè)視圖實(shí)體類,所以建議將它和普通的單表實(shí)體類放在不同的 package 下(這只是一個(gè)規(guī)范)。
然后我們的 Controller 中仍然只需一行代碼:
@RestController@RequestMapping("/order")publicclassOrderController{ @AutowiredprivateBeanSearcher beanSearcher; @GetMapping("/index")publicSearchResult
這就實(shí)現(xiàn)了一個(gè)支持高級(jí)查詢的 訂單接口,它同樣支持在上文區(qū)別二與區(qū)別三中所展示的各種檢索方式。
從本例可以看出,Bean Searcher 的檢索結(jié)果是 VO 對(duì)象,而非普通的單表實(shí)體類(DTO),這省去了 DTO 向 VO 的轉(zhuǎn)換過(guò)程,它可以直接返回給前端。
在事務(wù)性的接口用推薦使用 MyBatis Plus, 非事務(wù)的檢索接口中推薦使用 Bean Searcher
例如 創(chuàng)建訂單接口,在這個(gè)接口內(nèi)部同樣有很多查詢,比如你需要查詢 店鋪的是否已經(jīng)打烊,商品的庫(kù)存是否還足夠等,這些查詢場(chǎng)景,推薦依然使用 原有的 MyBatis Plus 或其它 ORM 就好,不必再用 Bean Seracher 了。
再如 訂單列表接口,純查詢,可能需要分頁(yè)、排序、過(guò)濾等功能,此時(shí)就可用 Bean Seracher 了。
Bean Searcher 默認(rèn)對(duì)實(shí)體類中的每個(gè)字段都支持了很多種檢索方式,但是我們也可以對(duì)它進(jìn)行約束。
例如,User實(shí)體類的name字段只允許 精確匹配 與 后模糊 查詢,則在name字段上添加一個(gè)注解即可:
@DbField(onlyOn = {Equal.class, StartWith.class})privateString name;
再如:不允許age字段參與 where 條件,則可以:
@DbField(conditional = false)privateintage;
參考:bs.zhxu.cn/guide/lates…
Bean Searcher 默認(rèn)允許按所有字段排序,但可以在實(shí)體類里進(jìn)行約束。例如,只允許按 age 字段降序排序:
@SearchBean(orderBy = "age desc", sortType = SortType.ONLY_ENTITY)publicclassUser{ // ...}
或者,禁止使用排序:
@SearchBean(sortType = SortType.ONLY_ENTITY)publicclassUser{ // ...}
參考:bs.zhxu.cn/guide/lates…
答:這并不是必須的,只是 Bean Searcher 的檢索方法接受這個(gè)類型的參數(shù)而已。如果你在 Controller 入?yún)⒛抢?用一個(gè) POJO 來(lái)接收也是可以的,只需要再用一個(gè)工具類把它轉(zhuǎn)換為Map即可,只不過(guò)平白多寫(xiě)了一個(gè)類而已,例如:
@GetMapping("/bs")publicList
這里為什么不直接使用 User 實(shí)體類來(lái)接收呢?因?yàn)?Bean Searcher 默認(rèn)支持很多參數(shù),而原有的User實(shí)體類中的字段不夠多,用它來(lái)接收的話會(huì)有很多參數(shù)接收不到。如果咱們的檢索需求比較簡(jiǎn)單,不需要前端指定那些參數(shù),則可以直接使用User實(shí)體類來(lái)接收。
這里的UserQuery可以這么定義:
// 繼承 User 里的字段publicclassUserQueryextendsUser{ // 附加:排序參數(shù) privateString order; privateString sort; // 附加:分頁(yè)參數(shù) privateInteger page; privateInteger size; // 附加:字段衍生參數(shù) privateString id_op; // 由于字段命名不能有中劃線,這里有下劃線替代 privateString name_op; // 前端傳參的時(shí)候就不能傳 name-op,而是 name_op 了 privateString name_ic; privateString age_op; // 省略其它附加字段... // 省略 Getter Setter 方法}
然后Utils工具類的toMap方法可以這樣寫(xiě)(這個(gè)工具類是通用的):
publicstaticMap
這樣就可以了,該接口依然可以支持很多種檢索方式:
GET /user/bs? name=Jack查詢 name等于Jack 的數(shù)據(jù)
GET /user/bs? name=Jack &name_ic=true查詢 name等于Jack 時(shí)忽略大小寫(xiě)
GET /user/bs? name=Jack &name_op=ct查詢 name包含Jack 的數(shù)據(jù)
GET /user/bs? age=20查詢 age等于20 的數(shù)據(jù)
GET /user/bs? age=20 &age_op=gt查詢 age大于20 的數(shù)據(jù)
等等...
注意使用參數(shù)是name_op,不再是name-op了
以上的方式應(yīng)該滿足了一些強(qiáng)迫癥患者的期望,但是這樣的代價(jià)是多寫(xiě)一個(gè)UserQuery類,這不禁讓我們細(xì)想:這樣做值得嗎?
當(dāng)然,寫(xiě)成這樣是有一些好處的:
便于參數(shù)校驗(yàn)
便于生成接口文檔
但是:
這是一個(gè)非事務(wù)性的檢索接口,參數(shù)校驗(yàn)真的那么必要嗎?本來(lái)就可以無(wú)參請(qǐng)求,參數(shù)傳錯(cuò)了系統(tǒng)自動(dòng)忽略它是不是也可以?
如果了解了 Bean Searcher 參數(shù)規(guī)則,是不是不用這個(gè)UserQuery類也可以生成文檔,或者在文檔中一句話概括該接口是 Bean Searcher 檢索接口,請(qǐng)按照規(guī)則傳遞參數(shù),是不是也行呢?
所以,我的建議是:一切以真實(shí)需求為準(zhǔn)則,不要為了規(guī)范而去規(guī)范,莫名徒增代碼。
看到這里,你可能已經(jīng)在心里準(zhǔn)備了一大堆的話想要反駁我,別急,我們先回顧一下前面的 [多表聯(lián)查] 章節(jié)所提到的:
Bean Searcher 中的實(shí)體類(SearchBean),實(shí)際上是一個(gè)可以 直接與 DB 有跨表映射關(guān)系 的VO(View Ojbect),它代表一種檢索業(yè)務(wù),在概念上它與傳統(tǒng) ORM 的實(shí)體類(Entity)或 域類(Domain)有著本質(zhì)的區(qū)別!
這一句話,道出了 Bean Searcher 的靈魂。它看似簡(jiǎn)單,實(shí)則難以悟透。如果您不是那種千古罕見(jiàn)先天通靈的奇才,強(qiáng)烈建議閱讀官方文檔的介紹 > 設(shè)計(jì)思想(出發(fā)點(diǎn))章節(jié),那里有對(duì)這一句話的詳細(xì)解釋。
答:當(dāng)然有。Bean Searcher 提供了一個(gè)參數(shù)構(gòu)建器,可讓后端人員想手動(dòng)添加或修改檢索參數(shù)時(shí)使用。例如:
@GetMapping("/bs")publicList
答:不存在的,Bean Searcher 是一個(gè)只讀 ORM,它也存在對(duì)象關(guān)系映射,所傳參數(shù)都是實(shí)體類內(nèi)定義的 Java 屬性名,而非數(shù)據(jù)庫(kù)表里的字段名(當(dāng)前端傳遞實(shí)體類未定義的字段參數(shù)時(shí),會(huì)被自動(dòng)忽略)。
也可以說(shuō):檢索參數(shù)與數(shù)據(jù)庫(kù)表是解耦的。
答:不會(huì)的,因?yàn)橛脩?strong>可獲取數(shù)據(jù)最多的請(qǐng)求就是無(wú)參請(qǐng)求,用戶嘗試的任何參數(shù),都只會(huì)縮小數(shù)據(jù)范圍,不可能擴(kuò)大。
如果想做數(shù)據(jù)權(quán)限,根據(jù)不同的用戶返回不同的數(shù)據(jù):可在參數(shù)過(guò)濾器里為權(quán)限字段統(tǒng)一注入條件(前提是 實(shí)體類中得有一個(gè)數(shù)據(jù)權(quán)限字段,可以在基類中定義)。
前段時(shí)間又不少朋友看了這篇文章私下問(wèn)我 Bean Searcher 的性能如何,這個(gè)周末我就在家做了下對(duì)比測(cè)試,結(jié)果如下:
比 Spring Data Jdbc 高5 ~ 10倍
比 Spring Data JPA 高2 ~ 3倍
比 原生 MyBatis 高1 ~ 2倍
比 MyBatis Plus 高2 ~ 5倍
完整報(bào)告:
github.com/troyzhxu/be…
gitee.com/troyzhxu/be…
以上測(cè)試是基于 H2 內(nèi)存數(shù)據(jù)庫(kù)進(jìn)行
測(cè)試源碼地址,大家可自行測(cè)試對(duì)比:
github.com/troyzhxu/be…
gitee.com/troyzhxu/be…
只要支持正常的 SQL 語(yǔ)法,都是支持的,另外 Bean Searcher 內(nèi)置了四個(gè)方言實(shí)現(xiàn):
分頁(yè)語(yǔ)法和 MySQL 一樣的數(shù)據(jù)庫(kù),默認(rèn)支持
分頁(yè)語(yǔ)法和 PostgreSql 一樣的數(shù)據(jù)庫(kù),選用 PostgreSql 方言 即可
分頁(yè)語(yǔ)法和 Oracle 一樣的數(shù)據(jù)庫(kù),選用 Oracle 方言 即可
分頁(yè)語(yǔ)法和 SqlServer(v2012+)一樣的數(shù)據(jù)庫(kù),選用 SqlServer 方言 即可
如果分頁(yè)語(yǔ)法獨(dú)創(chuàng)的,自定義一個(gè)方言,只需實(shí)現(xiàn)一個(gè)方法即可,參考:高級(jí) > SQL 方言章節(jié)。
上文所述的各種區(qū)別,并不是說(shuō) MyBatis Plus 和 Bean Searcher 哪個(gè)好哪個(gè)不好,而是它們專注的領(lǐng)域確實(shí)不一樣(BS 也不會(huì)替代 MP)。
Bean Searcher 在剛誕生的時(shí)候是專門(mén)用來(lái)處理那種特別復(fù)雜的檢索需求(如上文中的例圖所示),一般都用在管理后臺(tái)系統(tǒng)里。但用著用著,我們發(fā)現(xiàn),對(duì)檢索需求沒(méi)那么復(fù)雜的普通分頁(yè)查詢接口,Bean Searcher 也非常好用。代碼寫(xiě)起來(lái)比用傳統(tǒng)的ORM要簡(jiǎn)潔的多,只需一個(gè)實(shí)體類和Controller里的幾行代碼,Service和Dao什么的全都消失了,而且它返回的結(jié)果就是VO, 也不需要再做進(jìn)一步的轉(zhuǎn)換了,可以直接返回給前端。
在項(xiàng)目中配合使用它們,事務(wù)中使用 MyBatis Plus,列表檢索場(chǎng)景使用 Bean Searcher,你將如虎添翼。
實(shí)際上,在舊項(xiàng)目中集成 Bean Searcher 更加容易,已有的單表實(shí)體類都能直接復(fù)用,而多表關(guān)聯(lián)的 VO 對(duì)象類也只需添加相應(yīng)注解即可擁有強(qiáng)大的檢索能力。無(wú)論項(xiàng)目原來(lái) ORM 用的是 MyBatis, MP, 還是 Hibernate,Data Jdbc 等,也無(wú)論 Web 框架是 Spring Boot, Spring MVC 還是 Grails 或 Jfinal 等,只要是 java 項(xiàng)目, 都可以用它,為系統(tǒng)賦能高級(jí)查詢。
往期熱門(mén)文章:
1、大公司為什么禁止SpringBoot項(xiàng)目使用Tomcat?
2、快速交付神器:阿里巴巴官方低代碼引擎開(kāi)源了!
3、為什么 Spring和IDEA 都不推薦使用 @Autowired 注解
4、程序員的悲哀是什么?
5、被問(wèn)懵了:MySQL 自增主鍵一定是連續(xù)的嗎?
6、點(diǎn)一下詳情系統(tǒng)掛了,CPU100%
7、我說(shuō)用count(*)統(tǒng)計(jì)行數(shù),面試官讓我回去等消息...
8、世界第三大瀏覽器正在消亡
9、被問(wèn)懵了:MySQL 自增主鍵一定是連續(xù)的嗎?
10、FastJson 很好,但不適合我!
關(guān)鍵詞: