淺析Bean?Searcher?與?MyBatis?Plus?區(qū)別介紹
Bean Searcher 號(hào)稱 任何復(fù)雜的查詢都可以 一行代碼搞定,但 Mybatis Plus 似乎也有類似的動(dòng)態(tài)查詢功能,它們有怎樣的區(qū)別呢?
區(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 接口。
區(qū)別二(高級(jí)查詢)
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í)體類:
public class User { private long id; private String name; private int age; // 省略 Getter Setter }
1)使用 MyBatis Plus 查詢:
依賴:
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.1
首先要寫(xiě)一個(gè) Mapper 接口:
public interface UserMapper extends BaseMapper<User> { }
然后在 Controller 里寫(xiě)查詢接口:
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserMapper userMapper; @GetMapping("/mp") public List<User> mp(User user) { return userMapper.selectList(new QueryWrapper<>(user)); } }
此時(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}") private int age;
但加了注解后,age 就 只能 表達(dá) 大于 的關(guān)系了,不再可以表達(dá) 等于了。所以說(shuō),MyBatit Plus 的 字段運(yùn)算符 是 靜態(tài) 的,不能由參數(shù)動(dòng)態(tài)指定。
當(dāng)然我們可以在 Controller 里增加代碼讓它支持,但這樣代碼就不只一行了,檢索的需求越復(fù)雜,需要編寫(xiě)的代碼就越多了。
2)使用 Bean Searcher 查詢:
依賴:
implementation 'com.ejlchina:bean-searcher-boot-starter:3.6.0'
不用編寫(xiě)任何接口,復(fù)用同一個(gè)實(shí)體類,直接進(jìn)行查詢:
@RestController @RequestMapping("/user") public class UserController { @Autowired private BeanSearcher beanSearcher; @GetMapping("/bs") public List<User> bs(@RequestParam Map<String, Object> params) { // 你是否對(duì)入?yún)?Map 有偏見(jiàn)?如果有,請(qǐng)耐心往下看,有方案 return beanSearcher.searchList(User.class, params); } }
此時(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
可以傳哪些值?參閱這里:searcher.ejlchina.com/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) private int age;
這時(shí),當(dāng)前端只傳一個(gè) age
參數(shù)時(shí),執(zhí)行的 SQL 條件就是 age > ?
了,并且即使前端多傳一個(gè) age-op
參數(shù),也不再起作用了。
這其實(shí)是條件約束,下文會(huì)繼續(xù)講到。
區(qū)別三(邏輯分組)
就上文所例的代碼,除卻運(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)文體里。
分組功能非常強(qiáng)大,但如此復(fù)雜的檢索需求也確實(shí)罕見(jiàn),這里不再細(xì)述,詳情可閱:searcher.ejlchina.com/guide/lates…
區(qū)別四(多表聯(lián)查)
在不寫(xiě) SQL 的情況下,Mybatis Plus 的動(dòng)態(tài)查詢 僅限于 單表,而 Bean Searcher 單表 和 多表 都支持的一樣好。
這也是很重要的一點(diǎn)區(qū)別,因?yàn)?nbsp;大多數(shù)高級(jí)查詢 場(chǎng)景都是 需要聯(lián)表 的。
當(dāng)然有些人堅(jiān)持用 Mybatis Plus 的動(dòng)態(tài)查詢,為了避免聯(lián)表,從而在主表中冗余了很多字段,這不僅造成了 數(shù)據(jù)庫(kù)存儲(chǔ)空間壓力急劇增加,還讓項(xiàng)目更加難以維護(hù)。因?yàn)樵磾?shù)據(jù)一但變化,你必須同時(shí)更新這些冗余的字段,只要漏了一處,BUG 就跳出來(lái)了。
還是舉個(gè)例子,某訂單列表需要展示 訂單號(hào),訂單金額,店鋪名,買(mǎi)家名 等信息,用 Bean Searcher 實(shí)體類可以這么寫(xiě):
@SearchBean( tables = "order o, shop s, user u", // 三表關(guān)聯(lián) joinCond = "o.shop_id = s.id and o.buyer_id = u.id", // 關(guān)聯(lián)關(guān)系 autoMapTo = "o" // 未被 @DbField 注解的字段都映射到 order 表 ) public class OrderVO { private long id; // 訂單ID o.id private String orderNo; // 訂單號(hào) o.order_no private long amount; // 訂單金額 o.amount @DbField("s.name") private String shop; // 店鋪名 s.name @DbField("u.name") private String buyer; // 買(mǎi)家名 u.name // 省略 Getter Setter }
有心的同學(xué)會(huì)注意到,這個(gè)實(shí)體類的命名并不是 Order, 而是 OrderVO。這里只是一個(gè)建議的命名,因?yàn)樗潜举|(zhì)上就是一個(gè) VO,作用只是一個(gè)視圖實(shí)體類,所以建議將它和普通的單表實(shí)體類放在不同的 package 下(這只是一個(gè)規(guī)范)。
然后我們的 Controller 中仍然只需一行代碼:
@RestController @RequestMapping("/order") public class OrderController { @Autowired private BeanSearcher beanSearcher; @GetMapping("/index") public SearchResult<OrderVO> index(@RequestParam Map<String, Object> params) { // search 方法同時(shí)會(huì)返回滿足條件的總條數(shù) return beanSearcher.search(OrderVO.class, params); } }
這就實(shí)現(xiàn)了一個(gè)支持高級(jí)查詢的 訂單接口,它同樣支持在上文 區(qū)別二 與 區(qū)別三 中所展示的各種檢索方式。
從本例可以看出,Bean Searcher 的檢索結(jié)果是 VO 對(duì)象,而非普通的單表實(shí)體類(DTO),這 省去了 DTO 向 VO 的轉(zhuǎn)換過(guò)程,它可以直接返回給前端。
區(qū)別五(使用場(chǎng)景)
在事務(wù)性的接口用推薦使用 MyBatis Plus, 非事務(wù)的檢索接口中推薦使用 Bean Searcher
例如,創(chuàng)建訂單接口,在這個(gè)接口內(nèi)部同樣有很多查詢,比如你需要查詢 店鋪的是否已經(jīng)打烊,商品的庫(kù)存是否還足夠,這些查詢場(chǎng)景,推薦直接使用 原有的 MyBatis Plus 就好,不必再用 Bean Seracher 了。
疑問(wèn)
1)這貌似開(kāi)放很大的檢索能力,風(fēng)險(xiǎn)可控嗎?
Bean Searcher 默認(rèn)對(duì)實(shí)體類中的每個(gè)字段都支持了很多種檢索方式,但是我們也可以對(duì)它進(jìn)行約束。
條件約束
例如,User
實(shí)體類的 name
字段只允許 精確匹配 與 后模糊 查詢,則在 name
字段上添加一個(gè)注解即可:
@DbField(onlyOn = {Equal.class, StartWith.class}) private String name;
再如:不允許 age
字段參與 wehere 條件,則可以:
@DbField(conditional = false) private int age;
參考:searcher.ejlchina.com/guide/lates…
排序約束
Bean Searcher 默認(rèn)允許按所有字段排序,但可以在實(shí)體類里進(jìn)行約束。例如,只允許按 age 字段降序排序:
@SearchBean(orderBy = "age desc", sortType = SortType.ONLY_ENTITY) public class User { // ... }
或者,禁止使用排序:
@SearchBean(sortType = SortType.ONLY_ENTITY) public class User { // ... }
參考:searcher.ejlchina.com/guide/lates…
2)使用 Bean Searcher 后 Controller 的入?yún)⒈仨毷?Map 類型?
答:這 并不是必須的,只是 Bean Searcher 的檢索方法接受這個(gè)類型的參數(shù)而已。如果你在 Controller 入?yún)⒛抢?用一個(gè) POJO 來(lái)接收也是可以的,只需要再用一個(gè)工具類把它轉(zhuǎn)換為 Map
即可,只不過(guò) 平白多寫(xiě)了一個(gè)類 而已,例如:
@GetMapping("/bs") public List<User> bs(UserQuery query) { // 將 UserQuery 對(duì)象轉(zhuǎn)換為 Map 再傳入進(jìn)行檢索 return beanSearcher.searchList(User.class, Utils.toMap(query)); }
這里為什么不使用 User 實(shí)體類類接收呢? 因?yàn)?Bean Searcher 默認(rèn)支持很多參數(shù),而原有的
User
實(shí)體類中的字段不夠多,用它來(lái)接收的話會(huì)有很多參數(shù)無(wú)法接收。如果咱就是不想要哪些額外的參數(shù)可以直接使用User
實(shí)體類。
這里的 UserQuery
可以這么定義:
// 繼承 User 里的字段 public class UserQuery extends User { // 附加:排序參數(shù) private String order; private String sort; // 附加:分頁(yè)參數(shù) private Integer page; private Integer size; // 附加:字段衍生參數(shù) private String id_op; // 由于字段命名不能有中劃線,這里有下劃線替代 private String name_op; // 前端傳參的時(shí)候就不能傳 name-op,而是 name_op 了 private String name_ic; private String age_op; // 省略其它附加字段... // 省略 Getter Setter 方法 }
然后 Utils
工具類的 toMap
方法可以這樣寫(xiě)(這個(gè)工具類是通用的):
public static Map<String, Object> toMap(Object bean) { Map<String, Object> map = new HashMap<>(); Class<?> beanClass = bean.getClass(); while (beanClass != Object.class) { for (Field field : beanClass.getDeclaredFields()) { field.setAccessible(true); try { // 將下劃線轉(zhuǎn)換為中劃線 Strubg name = field.getName().replace('_', '-'); map.put(name, field.get(bean)); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } beanClass = beanClass.getSuperclass(); } return map; }
這樣就可以了,該接口依然可以支持很多種檢索方式:
- 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ī)范,莫名徒增代碼。
3)前端亂傳參數(shù)的話,存在 SQL 注入風(fēng)險(xiǎn)嗎?
答:不存在的,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ù)表是解耦的。
4)可以隨意傳參,會(huì)讓用戶獲取本不該看到的數(shù)據(jù)嗎?
答:不會(huì)的,因?yàn)橛脩?可獲取數(shù)據(jù)最多的請(qǐng)求就是無(wú)參請(qǐng)求,用戶嘗試的任何參數(shù),都只會(huì)縮小數(shù)據(jù)范圍,不可能擴(kuò)大。
如果想做 數(shù)據(jù)權(quán)限,根據(jù)不同的用戶返回不同的數(shù)據(jù):可在 Controller 層向檢索參數(shù)中手動(dòng)添加一個(gè)參數(shù)即可,或者在 ControllerAdvise 里注入限制參數(shù)也行。
總結(jié)
上文所述的各種區(qū)別,并不是要說(shuō) MyBatis Plus 和 Bean Searcher 那個(gè)好哪個(gè)不好,而是它們 專注的領(lǐng)域 確實(shí)不一樣。
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í)查詢。
最后附上 Bean Searcher 的相關(guān)連接:
- Bean Searcher 官方文檔(非常詳細(xì))
- 我這樣寫(xiě)代碼,比直接使用 MyBatis 效率提高了 100 倍(入門(mén)體驗(yàn))
- GitHub 源碼倉(cāng)庫(kù)(點(diǎn)個(gè)Star 吧)
- Gitee 源碼倉(cāng)庫(kù)(點(diǎn)個(gè)Star 吧)
作者:?jiǎn)桃玲u
鏈接:https://juejin.cn/post/7092411551507808264
來(lái)源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
到此這篇關(guān)于淺析Bean Searcher 與 MyBatis Plus 區(qū)別介紹的文章就介紹到這了,更多相關(guān)Bean Searcher 與 MyBatis Plus區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot與Postman實(shí)現(xiàn)REST模擬請(qǐng)求的操作
這篇文章主要介紹了SpringBoot與Postman實(shí)現(xiàn)REST模擬請(qǐng)求的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06使用Java對(duì)Hbase操作總結(jié)及示例代碼
這篇文章主要介紹了使用Java對(duì)Hbase進(jìn)行操作總結(jié),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07關(guān)于Java中properties文件編碼問(wèn)題
這篇文章主要介紹了關(guān)于Java中properties文件編碼問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11