一般做數(shù)據(jù)庫(kù)相關(guān)開(kāi)發(fā), 除非學(xué)習(xí), 否則很少有人愿意直接使用JDBC。本來(lái)Java代碼就比較啰嗦了,而直接用JDBC寫(xiě)代碼之啰嗦簡(jiǎn)直有些令人發(fā)狂!所以在實(shí)際開(kāi)發(fā)過(guò)程中,我們通常都會(huì)使用一些框架/庫(kù)來(lái)幫助我們操作數(shù)據(jù)庫(kù)。而且開(kāi)源市場(chǎng)上的選擇也比較多,就我個(gè)人接觸到的有:Hibernate,MyBatis,JdbcTemplate,DbUtils,ActiveRecord,JavaLite等等。 這些框架都能大幅的提高開(kāi)發(fā)效率,對(duì)于一些基本CRUD操作來(lái)說(shuō),雖然各有差異,但總的來(lái)說(shuō)基本是夠用了。
創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括克州網(wǎng)站建設(shè)、克州網(wǎng)站制作、克州網(wǎng)頁(yè)制作以及克州網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,克州網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到克州省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
然而對(duì)于稍微復(fù)雜點(diǎn)的數(shù)據(jù)查詢來(lái)說(shuō),總免不了需要手工編寫(xiě)SQL代碼,甚至還需要根據(jù)參數(shù)來(lái)動(dòng)態(tài)拼接SQL。各種框架基本上都有一套自己拼接動(dòng)態(tài)SQL的方案,也都能很輕松的將查詢出來(lái)的數(shù)據(jù)轉(zhuǎn)為對(duì)象(DTO)。
不過(guò)到目前為止,這些框架雖然能夠很輕松的幫助我們完成數(shù)據(jù)的映射,但是這些DTO還得需要我們手工一個(gè)個(gè)的去編寫(xiě)。
通常我們?cè)趯?xiě)完SQL的查詢代碼后, 需要有一個(gè)對(duì)應(yīng)的DTO,將數(shù)據(jù)庫(kù)中查詢出的數(shù)據(jù)映射到DTO,以便于調(diào)用的程序能夠更好的使用這些數(shù)據(jù)。當(dāng)然,為了省事,有時(shí)也會(huì)把數(shù)據(jù)直接存儲(chǔ)在像Map這樣的數(shù)據(jù)結(jié)構(gòu)中。不過(guò), Map這種方式雖然很輕便,但是會(huì)帶來(lái)幾個(gè)比重要的潛在問(wèn)題:
調(diào)用者需要記住Map里面每個(gè)key的名稱(chēng),這就會(huì)給程序員帶來(lái)一些所謂的記憶負(fù)擔(dān)
過(guò)重的記憶負(fù)擔(dān),就會(huì)導(dǎo)致系統(tǒng)的邏輯復(fù)雜,理解困難,維護(hù)更困難
SQL更改導(dǎo)致Key發(fā)生變化后,很難發(fā)現(xiàn)問(wèn)題,需要程序員非常小心的處理這些更改
如果想要避免Map帶來(lái)的這些問(wèn)題,我們需要為每個(gè)SQL查詢都單獨(dú)編寫(xiě)DTO。盡管書(shū)寫(xiě)這些DTO并沒(méi)有什么難度,但是非常枯燥乏味,特別是字段很多的時(shí)候更是如此;并且,如果SQL查詢的字段出現(xiàn)更改,也還是要記得回來(lái)修改這個(gè)DTO。單獨(dú)編寫(xiě)DTO雖然減輕了Map帶來(lái)的部分問(wèn)題,同時(shí)也額外增加了新的工作量。
如果有一種方法能夠在SQL代碼(包括動(dòng)態(tài)拼接的SQL)編寫(xiě)完成后,就自動(dòng)的做到下面2點(diǎn)就非常完美了:
根據(jù)SQL代碼,直接生成對(duì)應(yīng)的DTO
變更SQL代碼,自動(dòng)修改對(duì)應(yīng)的DTO
這樣,一方面解決了手工書(shū)寫(xiě)DTO的麻煩; 另一方面,當(dāng)修改SQL導(dǎo)致某個(gè)字段發(fā)生更改時(shí), 由于自動(dòng)生成的DTO也會(huì)同步修改,在那些引用到這個(gè)字段的地方,編譯器就會(huì)立即給出錯(cuò)誤提示! 使得問(wèn)題一產(chǎn)生就能立即被發(fā)現(xiàn),這樣可以避免了很多潛在的問(wèn)題。
本文正是試圖要解決如何根據(jù)SQL代碼自動(dòng)生成DTO的問(wèn)題,省去手工編寫(xiě)的麻煩,提高程序員的開(kāi)發(fā)效率。
理想總是很美好,現(xiàn)實(shí)總是很殘酷!
那么,到底能否實(shí)現(xiàn)這個(gè)想法呢,我們首先來(lái)初步分析一下自動(dòng)產(chǎn)生DTO的可行性:
要實(shí)現(xiàn)自動(dòng)產(chǎn)生DTO,其核心就是要拿到SQL查詢所對(duì)應(yīng)的每個(gè)列名及其數(shù)據(jù)類(lèi)型。有了列名和數(shù)據(jù)類(lèi)型,就能很容易寫(xiě)一個(gè)方法來(lái)產(chǎn)生DTO了。
我們知道,在一般情況下,SQL查詢寫(xiě)完之后,包括調(diào)用存儲(chǔ)過(guò)程和那些根據(jù)調(diào)用參數(shù)來(lái)動(dòng)態(tài)拼接的SQL,雖然最終運(yùn)行的SQL可能不盡相同,但是其查詢結(jié)果的字段部分都是相對(duì)固定的。
當(dāng)然,也有極少情況下會(huì)碰到字段都不確定的查詢,不過(guò)在這種極端情況下,即使手工也沒(méi)法寫(xiě)DTO了,反倒是用Map更合適, 我們這里不做討論。
那么,怎么才能拿到列名和類(lèi)型呢?
一種方案是分析SQL代碼中SELECT部分的字段,不過(guò)其局限性比較大:
對(duì)于拼接的SQL代碼,分析難度比較大
字段的類(lèi)型也難以判斷
SELECT * ...; CALL statement 這樣常見(jiàn)的查詢方式分析起來(lái)難度也很大
上述方案對(duì)像Mybatis這種采用配置文件(xml)來(lái)寫(xiě)SQL的方式,似乎有些可行性,我沒(méi)有具體試驗(yàn)過(guò),但估計(jì)面臨的困難不會(huì)少。
另一種方案是想辦法直接運(yùn)行包含SQL的這些代碼:
我們知道JDBC執(zhí)行一個(gè)SQL查詢,會(huì)返回ResultSet對(duì)象,通過(guò)該對(duì)象中的方法getMetaData(),能夠得到這次查詢的一些元數(shù)據(jù):如列名稱(chēng),列類(lèi)型,以及該列所在的表名等,這些信息就已經(jīng)足夠我們來(lái)產(chǎn)生需要的那個(gè)類(lèi)了。
那么,怎么才能夠運(yùn)行這些包含SQL的代碼呢?
對(duì)于那些固定的SQL語(yǔ)句還稍微好說(shuō)點(diǎn),我們拿到這個(gè)固定的SQL,調(diào)用JDBC就能拿到MetaData,然后就可以很容易的根據(jù)這些信息來(lái)生成DTO。但是,對(duì)于那些復(fù)雜的需要根據(jù)一系列參數(shù)來(lái)動(dòng)態(tài)產(chǎn)生的SQL查詢,在參數(shù)設(shè)置好前是無(wú)法直接運(yùn)行的,也就無(wú)法得到MetaData,得不到MetaData我們就無(wú)法生成DTO。
怎么辦?
前面已經(jīng)討論了,即便是動(dòng)態(tài)SQL,無(wú)論輸入什么樣的參數(shù),雖然執(zhí)行的SQL語(yǔ)句可能不一樣,但是最終產(chǎn)生結(jié)果列卻是固定的。 我們當(dāng)前需要解決的問(wèn)題不正是要獲取這些列信息嗎? 既然如此,那我們就構(gòu)造一系列默認(rèn)的參數(shù)值。這些參數(shù)并沒(méi)有實(shí)際用處,僅僅是為了讓我們正在編輯SQL代碼得以正常運(yùn)行,以便拿到需要的MetaData,至于能否查詢到數(shù)據(jù)并不緊要。
通常我們編寫(xiě)的SQL代碼,有2種存在形式:一是直接在Java代碼中, 另外一種是放在配置文件中。這里不討論哪種形式更好,以后我會(huì)單獨(dú)再找地方來(lái)討論。這里主要討論的是在Java代碼中拼接的SQL, 如何實(shí)現(xiàn)一個(gè)代碼生成器來(lái)自動(dòng)生成這些DTO:
要全自動(dòng)化的解決這個(gè)問(wèn)題,我們先來(lái)看看這個(gè)代碼生成器所要面臨的一些挑戰(zhàn)及應(yīng)對(duì)的思路:
如何標(biāo)識(shí)一段需要生成DTO的SQL代碼
首先,我們需要標(biāo)識(shí)出這段代碼,以便于代碼生成器可以運(yùn)行這段需要生成DTO代碼。而通常情況下,我們的數(shù)據(jù)接口都是方法級(jí)別的,因此我們可以通過(guò)對(duì)方法進(jìn)行注解,用注解來(lái)標(biāo)識(shí)這個(gè)方法要返回一個(gè)DTO對(duì)象是個(gè)不錯(cuò)的選擇。
如何定義DTO的類(lèi)名
一種很容易想到的方法就是通過(guò)SQL代碼所在的類(lèi)名+方法名自動(dòng)組合出一個(gè)名稱(chēng), 當(dāng)然有時(shí)為了靈活控制,應(yīng)該允許程序員指定一個(gè)名字。
如何執(zhí)行代碼
執(zhí)行代碼的關(guān)鍵是構(gòu)造一批能夠調(diào)用注解方法的合適參數(shù)。當(dāng)然首先需要對(duì)注解的方法進(jìn)行代碼分析,提取方法參數(shù)名及類(lèi)型。代碼分析可以用類(lèi)似JavaCC這樣的工具,或者一些語(yǔ)法分析器,這里不做細(xì)究。下面主要探討下默認(rèn)參數(shù)的構(gòu)造:
為了簡(jiǎn)化問(wèn)題,默認(rèn)情況下我們可以按如下規(guī)則進(jìn)行構(gòu)造:
數(shù)字型參數(shù),默認(rèn)為:0, 例如:public Object find(int arg){...} 構(gòu)造 int arg=0;
字符串參數(shù),默認(rèn)為:"", 構(gòu)造 String arg="";
布爾型參數(shù),默認(rèn)為:false, 構(gòu)造 boolean arg=false;
數(shù)組型參數(shù),默認(rèn)為:類(lèi)型[0], 構(gòu)造 int[] arg=new int[0];
對(duì)象型參數(shù),默認(rèn)為:new 類(lèi)型(), 例如:public Object find(User arg){...} 構(gòu)造 User arg=new User();
當(dāng)然,對(duì)于一些簡(jiǎn)單參數(shù)的情況下,上面構(gòu)造規(guī)則基本上都能夠奏效。 但是,對(duì)于有些參數(shù):比如參數(shù)是一個(gè)接口,或者是一個(gè)需要?jiǎng)討B(tài)連接的表名,又或者是SQL拼接代碼的邏輯要求參數(shù)必須是某些特殊值等等,默認(rèn)構(gòu)造出的參數(shù)就會(huì)導(dǎo)致程序無(wú)法執(zhí)行。
但是,怎么才能夠讓我們的代碼生成器能夠繼續(xù)執(zhí)行下去呢? 好像確實(shí)沒(méi)有什么能自動(dòng)處理的辦法,只好把這個(gè)問(wèn)題交給程序員來(lái)處理了,讓程序員來(lái)幫助代碼生成器完成參數(shù)的初始化。
我們可以在注解上提供一個(gè)參數(shù), 該參數(shù)主要完成對(duì)默認(rèn)規(guī)則下無(wú)法初始化的參數(shù)進(jìn)行設(shè)置。 當(dāng)然,這個(gè)參數(shù)中的初始化代碼也可以覆蓋默認(rèn)規(guī)則,以便于我們?cè)诰庉嬰A段就可以測(cè)試執(zhí)行不同的SQL流程。
如何生成DTO
經(jīng)過(guò)以上一系列的處理,我們終于能自動(dòng)的把包含SQL查詢代碼的方法運(yùn)行起來(lái)了。不過(guò),現(xiàn)在我們還沒(méi)得到想要的MetaData,還無(wú)法生成DTO。
一種可能的方式是包裝一個(gè)JDBC,截獲本次方法調(diào)用時(shí)執(zhí)行的SQL查詢, 但面臨的問(wèn)題是,如果方法中有多次查詢就比較麻煩了。
另一種方式依賴(lài)于框架的支持,可以截獲到方法的return語(yǔ)句,獲取其執(zhí)行的SQL語(yǔ)句, 有了SQL語(yǔ)句,生成DTO就沒(méi)有什么難度了。
如何修改代碼
為了盡量減少程序員的工作,我們的代碼生成器在生成完DTO后, 還需要將方法的返回值自動(dòng)修改成這個(gè)DTO類(lèi)。
如何處理SQL的變更
簡(jiǎn)單的做法是:一旦有某個(gè)SQL代碼發(fā)生變化,就把所有的DTO都按照前面的方法重新生成一遍。 不過(guò),很顯然當(dāng)查詢方法很多的時(shí)候,DTO代碼生成的過(guò)程將緩慢到難以忍受。
另外一種更合理的做法是:我們?cè)谏蒁TO時(shí)增加一個(gè)指紋字段,其值可以用SQL代碼中所包含的信息來(lái)產(chǎn)生,例如:代碼長(zhǎng)度+代碼的hashCode.代碼生成器在決定是否需要處理這個(gè)方法前,先計(jì)算該方法的指紋和存在于DTO里面的指紋進(jìn)行比較,如果相同就跳過(guò),否則就認(rèn)為本方法的SQL發(fā)生了變更,需要更新DTO。
到此為止,基本上DTO代碼生成器的主要障礙都有了相應(yīng)的處理辦法。最后,我們用一個(gè)具體的實(shí)現(xiàn)來(lái)做個(gè)簡(jiǎn)單示例。
這里需要引入2個(gè)項(xiàng)目:
monalisa-db: https://github.com/11039850/monalisa-db
這是一個(gè)非常簡(jiǎn)單的ORM框架,通過(guò)@DB(jdbc_url,username,password)注解來(lái)引入數(shù)據(jù)庫(kù),同時(shí)也實(shí)現(xiàn)了對(duì)數(shù)據(jù)庫(kù)的一些基本操作。
monalisa-eclipse: https://github.com/11039850/monalisa-eclipse
這是一個(gè)相應(yīng)的Eclipse插件,它可以:
@DB注解的接口,在文件保存時(shí) ,自動(dòng)生成表的CRUD操作
@Select注解的方法,在文件保存時(shí) ,自動(dòng)生成DTO
很輕松的書(shū)寫(xiě)多行字符串
插件安裝和設(shè)置可以參考: https://github.com/11039850/monalisa-db/wiki/Code-Generator
下面是一個(gè)根據(jù)動(dòng)態(tài)SQL自動(dòng)生成DTO示例,完整的例子工程可以參考: https://github.com/11039850/monalisa-example
package test.dao;
public class UserBlogDao { //@Select 注解指示該方法需自動(dòng)生成DTO
//默認(rèn)類(lèi)名: Result + 方法名, 默認(rèn)包名:數(shù)據(jù)訪問(wèn)類(lèi)的包名+"."+數(shù)據(jù)訪問(wèn)類(lèi)的名稱(chēng)(小寫(xiě))
//可選參數(shù):name 指定生成結(jié)果類(lèi)的名稱(chēng),如果未指定該參數(shù),則采用默認(rèn)類(lèi)名
//可選參數(shù):build 初始化調(diào)用參數(shù)的Java片段代碼,替換默認(rèn)的參數(shù)構(gòu)造規(guī)則
@Select(name="test.result.UserBlogs")
//!!! 保存后會(huì)自動(dòng)修改該函數(shù)的返回值為: List -> List<UserBlogs>
//第一次編寫(xiě)時(shí),由于結(jié)果類(lèi)還不存在, 為了保證能夠編譯正常,
//函數(shù)的返回值 和 查詢結(jié)果要用 泛值 替代, 保存后,插件會(huì)自動(dòng)修改.
//函數(shù)的返回值 和 查詢結(jié)果 泛值的對(duì)應(yīng)關(guān)系分三類(lèi)如下:
//1. List查詢
//public DataTable method_name(...){... return Query.getList(); } 或
//public List method_name(...){... return Query.getList(); }
//
//2. Page查詢
//public Page method_name(...){... return Query.Page(); }
//
//3. 單條記錄
//public Object method_name(...){... return Query.getResult(); }
//
public List selectUserBlogs(int user_id){
Query q=TestDB.DB.createQuery();
q.add(""/**~{
SELECT a.id,a.name,b.title, b.content,b.create_time
FROM user a, blog b
WHERE a.id=b.user_id AND a.id=?
}*/, user_id);
return q.getList();
}
}
上述代碼保存后,插件就會(huì)自動(dòng)生成一個(gè)DTO類(lèi):test.result.UserBlogs, 并自動(dòng)將方法修改成如下的聲明:
public List<UserBlogs> selectUserBlogs(int user_id){
...
return q.getList(UserBlogs.class);
}
當(dāng)然,如果對(duì)selectUserBlogs方法做了任何的修改(包括只是加了一個(gè)空格),保存文件后,插件也會(huì)自動(dòng)更新UserBlogs。
同時(shí),為了方便我們調(diào)試,插件也會(huì)在Eclipse的控制臺(tái)窗口輸出類(lèi)似下面的信息:
2016-06-27 17:00:31 [I] ****** Starting generate result classes from: test.dao.UserBlogDao ******
2016-06-27 17:00:31 [I] Create class: test.result.UserBlogs, from: [selectUserBlogs(int)]
SELECT a.id,a.name,b.title, b.content,b.create_time
FROM user a, blog b
WHERE a.id=b.user_id AND a.id=0
順便補(bǔ)充一下:
在Java代碼中書(shū)寫(xiě)SQL,非常令人討厭的一件事情就是Java語(yǔ)言中字符串的連接問(wèn)題。使得大段的SQL代碼中間要插很多的換行/轉(zhuǎn)義符號(hào),寫(xiě)起來(lái)很麻煩,看著也不舒服。monalisa-eclipse插件順便也解決了多行字符串的書(shū)寫(xiě)問(wèn)題。
例如:
System.out.println(""/**~{
SELECT *
FROM user
WHERE name="zzg"
}*/);
將會(huì)輸出:
SELECT *
FROM user
WHERE name="zzg"
當(dāng)然,為了快速書(shū)寫(xiě),可以在Eclipse中把多行字符串的語(yǔ)法設(shè)置為一個(gè)代碼模板。關(guān)于多行語(yǔ)法的更多細(xì)節(jié)可以參考:https://github.com/11039850/monalisa-db/wiki/Multiple-line-syntax
到這里,動(dòng)態(tài)SQL代碼自動(dòng)生成DTO的思路和實(shí)現(xiàn)例子基本上就介紹完了, 歡迎大家提出各種有理無(wú)理的意見(jiàn),一起討論、進(jìn)步,謝謝!
新聞標(biāo)題:如何根據(jù)動(dòng)態(tài)SQL代碼自動(dòng)生成DTO
鏈接地址:http://www.rwnh.cn/article4/gdohie.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、定制開(kāi)發(fā)、關(guān)鍵詞優(yōu)化、網(wǎng)站排名、網(wǎng)站導(dǎo)航、虛擬主機(jī)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)