? ? ? ? ?最近項目組在驗證一個應(yīng)用場景精居,讓自己的應(yīng)用在MySQL和Oracle數(shù)據(jù)庫都能夠正常運行(你可以認(rèn)為我們沒事吃飽撐著玩)。首先感謝就是沒有用到存儲過程潜必,要不真的是要吐血了靴姿。其次使用MyBatis來做數(shù)據(jù)庫訪問的中間層,才能同時支持MySQL和Oracle刮便,即使如此空猜,這種同時支持也讓我們感到行大于知,做比想難恨旱。這種差異主要還是因為兩種數(shù)據(jù)庫的不同造成應(yīng)用SQL語句寫法上的差異辈毯,例如Oracle不允許寫雙引號的常量如where user_id="123",但是MySQL則是正常搜贤,為了兼容谆沃,我們需要使用單引號,下面我們把碰到的一些主要問題記錄下來仪芒,為了能存在自己的記憶中唁影。
1耕陷、關(guān)于日期類型的操作
? ? ? ?由于MySQL在插入yyyy-mm-dd HH:mm:ss格式的日期數(shù)據(jù)時候,對于映射的數(shù)據(jù)可以定義為String類型据沈,MySQL驅(qū)動會自動將字符串的日期數(shù)據(jù)轉(zhuǎn)換為MySQL的日期定義格式哟沫,但是Oracle則不允許將字符串?dāng)?shù)據(jù)直接映射為Oracle定義的日期列,必須通過SQL語句中使用to_date和to_char函數(shù)實現(xiàn)日期和字符的相互轉(zhuǎn)換锌介。這樣導(dǎo)致MyBatis中SQL寫法的不一致嗜诀。
? ? ? ?為了統(tǒng)一SQL寫法,對于映射為日期類型列的Java對應(yīng)數(shù)據(jù)需要定義為Date類型孔祸,這樣在MyBatis做參數(shù)映射Bind的時候隆敢,兩個數(shù)據(jù)庫的驅(qū)動都會支持Date類型的Java對象數(shù)據(jù)插入,從而避免了SQL語句的差別崔慧。至于將前端提交的日期字符串?dāng)?shù)據(jù)自動的映射到日期字段拂蝎,Spring的K/V映射對象需要擴(kuò)展Convert(String,Data)接口,對于JSON傳入的數(shù)據(jù)惶室,一般JSON解析器可以根據(jù)相關(guān)的Annotation注釋將字符串日期數(shù)據(jù)轉(zhuǎn)換為Date對象温自,這需要應(yīng)用底層支撐.例如fastjson在定義日期對象createDate上通過JSONField定義了日期轉(zhuǎn)字符串的默認(rèn)格式。
@JSONField(yyyy-MM-dd HH:mm:ss)
Date createDate; //創(chuàng)建時間
2皇钞、關(guān)于一次插入多記錄的操作
? ? ? ? ?MySQL能夠方便的將一個List<T>對象中的多條記錄通過一條SQL語句插入一張表捣作,提高了整個速度,這種插入和Java的Batch插入多條記錄不一致鹅士,是一次交互,一條語句搞定惩坑,只是Mysql默認(rèn)對一次交互的SQL語句長度有限制掉盅,默認(rèn)為1M,所以List中的記錄條數(shù)不能過多以舒,否則報錯 nested exception is com.mysql.jdbc.PacketTooBigException: Packet for query is too large (5677854 > 1048576).?You can change this value on the server by setting the max_allowed_packet' variable趾痘。
? ? ? ?另外一種場景,對于EXCEL導(dǎo)入這種業(yè)務(wù)蔓钟,如果所有記錄當(dāng)一個事務(wù)更新永票,我們會使用MyBatis的批量機(jī)制,類似如下滥沫,但是執(zhí)行的效率肯定沒有下面一條語句插入的速度快(見下面的foreach語法)侣集,尤其記錄數(shù)據(jù)到5000,10000條的時候,兩種處理方式的時間差會更加明顯兰绣。
SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
? ? UserDao mapper = sqlSession.getMapper(UserDao.class);
? ? for (int i = 0; i < 500; i++) {
? ? ? ? user = new User();
? ? ? ? user.setId("test" + i);
? ? ? ? user.setName("name" + i);
? ? ? ? mapper.insert(user);
? ? }
? ? sqlSession.commit();
? ? ? ?由于兩個數(shù)據(jù)庫在處理這種多記錄插入語句的不同世分,我們可以充分利用MyBatis的databaseId這個屬性,通過if等表達(dá)式缀辩,在xml文件中臭埋,通過databaseId執(zhí)行不同的sql語句踪央,這可以作為跨數(shù)據(jù)庫通用的解決方案。注意這種批量插入的記錄數(shù)據(jù)的寫法瓢阴,記錄數(shù)不能過多畅蹂,避免超過單條SQL語句的長度,大家可以猜猜_databaseId這個屬性怎么配置來(度娘肯定知道)荣恐。
? ? ? ?對于我們而言液斜,需要明白MyBatis加載如下定義和上述定義的區(qū)別,下面的配置代表初始化過程會根據(jù)連接數(shù)據(jù)庫種類的不同募胃,優(yōu)先加載與該數(shù)據(jù)庫種類匹配的語句定義旗唁,在連接ORACLE數(shù)據(jù)的時候,將不加載此文件痹束。
3.關(guān)于數(shù)據(jù)庫翻頁記錄的操作
? ? ? ? ?這個應(yīng)該是兩最明顯的區(qū)別了检疫,很多人都知道MySql中分頁很簡單,因為它利用關(guān)鍵字limit來實現(xiàn)分頁查詢祷嘶;但是Oracle實現(xiàn)起來就比MySql要繁瑣得多了屎媳,在每個結(jié)果集中只有一個rownum字段標(biāo)明它的位置,并且只能用rownum<=某個數(shù)论巍,不能用rownum>=某個數(shù)烛谊,因為ROWNUM是偽列,在使用時所以需要為ROWNUM取一個別名嘉汰,變成邏輯列丹禀,然后來操作。例如:
select * from (select ROWNUM as num,A.* from (select * from testtable) A where ROWNUM<=40) where num >= 20;
? ? ? ? 由于兩個數(shù)據(jù)庫對于翻頁語句處理的不同鞋怀,雖然我們可以利用databaseId關(guān)鍵字寫出兩種不同的翻頁語句双泪,而且必然是兩條語句,一條查出符合條件的記錄總數(shù)密似,所以代碼實現(xiàn)會有一定的復(fù)雜和重復(fù)讀焙矛。所以更多的時候,應(yīng)用會擴(kuò)展MyBatis的插件Plugin機(jī)制残腌,對StamentHandler對象進(jìn)行攔截村斟,根據(jù)開發(fā)人員傳入的select語句,自動匹配數(shù)據(jù)庫的種類抛猫,完成翻頁記錄總數(shù)據(jù)的查詢蟆盹,這種機(jī)制根據(jù)傳入的select語句抽取,得出總的toatal記錄數(shù)邑滨,然后再自動組裝翻頁語句日缨,簡化了應(yīng)用開發(fā),只是查詢總數(shù)和封裝翻頁對象的過程交給了基礎(chǔ)組件掖看,也屏蔽了數(shù)據(jù)庫的差異匣距。
? ? ? ?以上幾種場景是目前我們在使用MyBatis跨平臺使用過程中碰到的問題面哥,在沒有實踐之前,總以為沒有存儲過程使用毅待,就可以很快在ORACLE和MySQL之間轉(zhuǎn)換尚卫,其實不然,還有較多細(xì)節(jié)需要關(guān)注尸红,底層框架也需要擴(kuò)展吱涉,尤其在翻頁交易場景中。其實在真正上線過程中外里,應(yīng)用可能還會擴(kuò)展Plugin機(jī)制怎爵,記錄超過指定執(zhí)行時間或者結(jié)果集的SQL語句,以協(xié)助應(yīng)用和數(shù)據(jù)庫之間發(fā)生問題時的場景分析盅蝗。