做舞臺(tái)燈光的在哪些網(wǎng)站接訂單呢今日新聞?wù)?0字
這篇文章,我們聊聊 Mybatis 動(dòng)態(tài) SQL ?,以及我對(duì)于編程技巧的幾點(diǎn)思考 ,希望對(duì)大家有所啟發(fā)。
1 什么是 Mybatis 動(dòng)態(tài)SQL
如果你使用過 JDBC 或其它類似的框架,你應(yīng)該能理解根據(jù)不同條件拼接 SQL 語句有多痛苦,例如拼接時(shí)要確保不能忘記添加必要的空格,還要注意去掉列表最后一個(gè)列名的逗號(hào)。
Mybatis ?借助功能強(qiáng)大?OGNL?表達(dá)式,可以根據(jù)參數(shù)條件,動(dòng)態(tài)生成執(zhí)行 SQL 。
使用動(dòng)態(tài) SQL 最常見情景是根據(jù)條件包含 where 子句的一部分。
這條語句提供了可選的查詢博客文章列表 ,如果不傳入 “title”,那么所有處于 “ACTIVE” 狀態(tài)的 博客都會(huì)返回。
如果傳入了 “title” 參數(shù),那么就會(huì)對(duì) “title” 一列進(jìn)行模糊查找并返回對(duì)應(yīng)的 BLOG 結(jié)果。
如果希望通過 “title” 和 “author” 兩個(gè)參數(shù)都可以搜索,只需要加入另一個(gè)條件即可,見下圖:
我們也可以使用 where 標(biāo)簽,該標(biāo)簽只會(huì)在子元素返回任何內(nèi)容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 標(biāo)簽也會(huì)將它們?nèi)コ?/p>
Mybatis 還支持 choose (when, otherwise)、trim?(where, set)、foreach 等其他的動(dòng)態(tài)標(biāo)簽,這里就不一一贅述了。
2 人生第一次線上OOM事故
我曾服務(wù)一家電商公司的用戶中心,用戶中心提供用戶注冊(cè),查詢,修改等基礎(chǔ)功能 。
那個(gè)時(shí)候 Dubbo 等 RPC 框架并沒有開源,所有的服務(wù)都以 HTTP 接口形式提供,數(shù)據(jù)傳輸格式是 XML 。
因?yàn)閷懡涌诜浅YM(fèi)勁,所以為了接口復(fù)用,我寫了一個(gè)通用接口 getUserByConditions ,該接口支持通過 「用戶名」、「昵稱」、「手機(jī)號(hào)」、「用戶編號(hào)」這三個(gè)查詢用戶基本信息。
使用的是?ibatis?(mybatis 的前身), SQLMap 見下圖 。
當(dāng)構(gòu)建動(dòng)態(tài) SQL 查詢時(shí),條件通常會(huì)追加到?WHERE
?子句后,而以?WHERE 1 = 1
?開頭,可以輕松地使用?AND
?追加其他條件。
用戶中心在上線后,竟然每隔三四個(gè)小時(shí)就發(fā)生了內(nèi)存溢出問題 ,經(jīng)過通過和 DBA 溝通,發(fā)現(xiàn)高頻次出現(xiàn)全表查用戶表,執(zhí)行 SQL 變成 :
查看日志后,發(fā)現(xiàn)前端傳遞的參數(shù)出現(xiàn)了空字符串,筆者在代碼中并沒有做參數(shù)校驗(yàn),所以才出現(xiàn)全表查詢 ,當(dāng)時(shí)用戶表的數(shù)據(jù)是 1000多萬 ,頁面調(diào)用幾次,用戶中心服務(wù)就 OOM 了。
最終解決問題的方式很簡單,后端在接收參數(shù)時(shí),做了參數(shù)校驗(yàn)。
事故雖然解決了,但對(duì)我的影響一直延續(xù)至今。我仍然記得當(dāng)時(shí)站在運(yùn)維同學(xué)旁邊,不斷的看他調(diào)整 JVM 參數(shù) ,重啟服務(wù)的畫面,自己那個(gè)時(shí)候真的是羞愧難當(dāng),心中發(fā)誓:以后絕對(duì)不可以發(fā)生類似的事故。
對(duì)于動(dòng)態(tài) SQL ,我的編程思維也經(jīng)歷了如下三個(gè)階段 :
-
前后端參數(shù)校驗(yàn)
-
復(fù)用和專用要做平衡
-
防御性編程意識(shí)
3 前后端參數(shù)校驗(yàn)
為了提升開發(fā)效率,我們?nèi)藶榈膶⑾到y(tǒng)分為前端、后端,分別由兩撥不同的人員開發(fā) ,經(jīng)常出現(xiàn)系統(tǒng)問題時(shí),兩撥人都非常不服氣,相互指責(zé)。
要想系統(tǒng)健壯,前后端應(yīng)該同時(shí)做接口參數(shù)校驗(yàn) (后端必須做參數(shù)校驗(yàn)),當(dāng)大家都遵循這個(gè)規(guī)約時(shí),出現(xiàn)系統(tǒng)問題的風(fēng)險(xiǎn)大大減少。
1、前端校驗(yàn)
前端校驗(yàn),主要是為了提高用戶體驗(yàn),例如用戶輸入一個(gè)郵箱地址,要校驗(yàn)這個(gè)郵箱地址是否合法,沒有必要發(fā)送到服務(wù)端進(jìn)行校驗(yàn),直接在前端用 js 進(jìn)行校驗(yàn)即可。
但是大家需要明白的是,前端校驗(yàn)無法代替后端校驗(yàn),前端校驗(yàn)可以有效的提高用戶體驗(yàn),但是無法確保數(shù)據(jù)完整性,因?yàn)樵?B/S 架構(gòu)中,用戶可以方便的拿到請(qǐng)求地址,然后直接發(fā)送請(qǐng)求,傳遞非法參數(shù)。
2、后端校驗(yàn)
后端必須做參數(shù)校驗(yàn),后端必須做參數(shù)校驗(yàn),后端必須做參數(shù)校驗(yàn),重要的事情表達(dá)三次。
數(shù)據(jù)在網(wǎng)絡(luò)傳輸過程中有可能被篡改了,或者數(shù)據(jù)不滿足業(yè)務(wù)需求,假如不做后端參數(shù)校驗(yàn),則有可能導(dǎo)致系統(tǒng)異常,或者業(yè)務(wù)出現(xiàn)重大事故。
比如在 SpringBoot 項(xiàng)目中,我們可以使用?hibernate-validator?進(jìn)行參數(shù)校驗(yàn) 。
POST、PUT 請(qǐng)求一般會(huì)使用 requestBody 傳遞參數(shù),這種情況下,后端使用 DTO 對(duì)象進(jìn)行接收。
只要給 DTO 對(duì)象加上 @Validated 注解就能實(shí)現(xiàn)自動(dòng)參數(shù)校驗(yàn)。比如,有一個(gè)保存 User 的接口,要求 userName 長度是 2-10,account 和 password 字段長度
是 6-20。
在 DTO 字段上聲明約束注解:
在方法參數(shù)上聲明校驗(yàn)注解:
雖然,我們可以使用接口校驗(yàn),可以保證動(dòng)態(tài) SQL 的參數(shù)正確,但是假如我們僅僅只是復(fù)用 SQLMap (Dao 方法)時(shí),也有可能因?yàn)檎{(diào)用方傳遞參數(shù)錯(cuò)誤,導(dǎo)致非預(yù)期的問題。
當(dāng)然,我們也可以使用 Mybatis 攔截器從根本上來解決,但是我想這樣會(huì)加大系統(tǒng)的復(fù)雜度。于是,我思考了了另外一點(diǎn):復(fù)用和專用要做平衡。
4 復(fù)用和專用要做平衡
我當(dāng)時(shí)寫的那個(gè)接口 getUserByConditions ,是支持四種不同參數(shù)的查詢,同樣也是為了省時(shí)間,快點(diǎn)出活。
后來,隨著我工作經(jīng)驗(yàn)的日益豐富,我的編程習(xí)慣也慢慢發(fā)生了改變,對(duì)于業(yè)務(wù)需求明確的場景,我更多的傾向于將通用接口拆分成專用接口。
比如 getUserByConditions 可以拆分成如下四個(gè)接口 ,
-
按照用戶 ID 查詢用戶信息
-
按照用戶昵稱查詢用戶信息
-
按照手機(jī)號(hào)查詢用戶信息
-
按照用戶名查詢用戶信息
比如按照用戶 ID 查詢用戶信息 , SQLMAP 就簡化為:
通過這樣的拆分,我們的接口設(shè)計(jì)更加細(xì)粒度,也更容易維護(hù) , 同時(shí)也可以規(guī)避 where 1 =1 產(chǎn)生的不確定性(雖然我做了后端校驗(yàn),依然存在不確定性)。
有的同學(xué)會(huì)有疑問:假如拆分得太細(xì),會(huì)不會(huì)增加我編寫接口和 SQLMap 的工作量 ?
筆者的思路是:定制自己的代碼生成器,將生成的 SQLMap ?、Mapper 保證更細(xì)的顆粒度。
5 防御性編程意識(shí)
筆者剛?cè)胄械臅r(shí)候,只是機(jī)械性的完成任務(wù),并沒有思考代碼后面的資源占用,以及有沒有可能產(chǎn)生惡劣的影響。
隨著見識(shí)更多的系統(tǒng),學(xué)習(xí)開源項(xiàng)目,筆者慢慢培養(yǎng)了一種習(xí)慣:
-
這段代碼會(huì)占用多少系統(tǒng)資源
-
如何規(guī)避風(fēng)險(xiǎn) ,做好預(yù)防性編程。
其實(shí),這和玩游戲差不多 ,在玩游戲的時(shí),我們經(jīng)常說一個(gè)詞,那就是意識(shí)。
上圖,后裔跟墨子在壓對(duì)面馬可蔡文姬,看到小地圖中路鎧跟小喬的視野,方向是往下路來的,這時(shí)候我們就得到了一個(gè)信息。
知道對(duì)面的人要來抓,或者是協(xié)防,這種情況我們只有兩個(gè)人,其他的隊(duì)友都不在,只能選擇避戰(zhàn),強(qiáng)打只會(huì)損失兩名“大將”。
通過小地圖的信息,并且想出應(yīng)對(duì)方法,就是叫做“猜測意識(shí)”。
編程也是一樣的,我們思考代碼可能產(chǎn)生的系統(tǒng)資源占用,以及可能存在的風(fēng)險(xiǎn),并做好防御性編程,就是編程的意識(shí)。
6 寫到最后
人生第一次線上 OOM 事故,因我在使用 Mybatis 動(dòng)態(tài) SQL 時(shí),沒有做后端校驗(yàn)而出現(xiàn),造成了比較壞的影響。
在后面的職業(yè)生涯里面,為了規(guī)避生產(chǎn)環(huán)境的事故,我試著打磨自己的編程思維,比如做好后端校驗(yàn)、平衡好復(fù)用和專用接口、培養(yǎng)防御性編程的意識(shí)?。
絮絮叨叨這么多,大家可能覺得我小題大做了,但事實(shí)是,類似我這樣的事故層出不窮,上周我又目睹了一起。
IT 世界有了那么多框架,好像我們依然寫不好代碼,我有點(diǎn)沮喪。