成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

如何根據(jù)動態(tài)SQL代碼自動生成DTO

weij / 1022人閱讀

摘要:如何修改代碼為了盡量減少程序員的工作,我們的代碼生成器在生成完后,還需要將方法的返回值自動修改成這個類。具體的實現(xiàn)到此為止,基本上代碼生成器的主要障礙都有了相應(yīng)的處理辦法。

當(dāng)前的狀況

一般做數(shù)據(jù)庫相關(guān)開發(fā), 除非學(xué)習(xí), 否則很少有人愿意直接使用JDBC。本來Java代碼就比較啰嗦了,而直接用JDBC寫代碼之啰嗦簡直有些令人發(fā)狂!所以在實際開發(fā)過程中,我們通常都會使用一些框架/庫來幫助我們操作數(shù)據(jù)庫。而且開源市場上的選擇也比較多,就我個人接觸到的有:Hibernate,MyBatis,JdbcTemplate,DbUtils,ActiveRecord,JavaLite等等。 這些框架都能大幅的提高開發(fā)效率,對于一些基本CRUD操作來說,雖然各有差異,但總的來說基本是夠用了。

然而對于稍微復(fù)雜點的數(shù)據(jù)查詢來說,總免不了需要手工編寫SQL代碼,甚至還需要根據(jù)參數(shù)來動態(tài)拼接SQL。各種框架基本上都有一套自己拼接動態(tài)SQL的方案,也都能很輕松的將查詢出來的數(shù)據(jù)轉(zhuǎn)為對象(DTO)。

不過到目前為止,這些框架雖然能夠很輕松的幫助我們完成數(shù)據(jù)的映射,但是這些DTO還得需要我們手工一個個的去編寫。

存在的問題

通常我們在寫完SQL的查詢代碼后, 需要有一個對應(yīng)的DTO,將數(shù)據(jù)庫中查詢出的數(shù)據(jù)映射到DTO,以便于調(diào)用的程序能夠更好的使用這些數(shù)據(jù)。當(dāng)然,為了省事,有時也會把數(shù)據(jù)直接存儲在像Map這樣的數(shù)據(jù)結(jié)構(gòu)中。不過, Map這種方式雖然很輕便,但是會帶來幾個比重要的潛在問題:

調(diào)用者需要記住Map里面每個key的名稱,這就會給程序員帶來一些所謂的記憶負(fù)擔(dān)

過重的記憶負(fù)擔(dān),就會導(dǎo)致系統(tǒng)的邏輯復(fù)雜,理解困難,維護(hù)更困難

SQL更改導(dǎo)致Key發(fā)生變化后,很難發(fā)現(xiàn)問題,需要程序員非常小心的處理這些更改

如果想要避免Map帶來的這些問題,我們需要為每個SQL查詢都多帶帶編寫DTO。盡管書寫這些DTO并沒有什么難度,但是非常枯燥乏味,特別是字段很多的時候更是如此;并且,如果SQL查詢的字段出現(xiàn)更改,也還是要記得回來修改這個DTO。多帶帶編寫DTO雖然減輕了Map帶來的部分問題,同時也額外增加了新的工作量。

如果有一種方法能夠在SQL代碼(包括動態(tài)拼接的SQL)編寫完成后,就自動的做到下面2點就非常完美了:

根據(jù)SQL代碼,直接生成對應(yīng)的DTO

變更SQL代碼,自動修改對應(yīng)的DTO

這樣,一方面解決了手工書寫DTO的麻煩; 另一方面,當(dāng)修改SQL導(dǎo)致某個字段發(fā)生更改時, 由于自動生成的DTO也會同步修改,在那些引用到這個字段的地方,編譯器就會立即給出錯誤提示! 使得問題一產(chǎn)生就能立即被發(fā)現(xiàn),這樣可以避免了很多潛在的問題。

本文正是試圖要解決如何根據(jù)SQL代碼自動生成DTO的問題,省去手工編寫的麻煩,提高程序員的開發(fā)效率。

解決的思路

理想總是很美好,現(xiàn)實總是很殘酷!

那么,到底能否實現(xiàn)這個想法呢,我們首先來初步分析一下自動產(chǎn)生DTO的可行性:

要實現(xiàn)自動產(chǎn)生DTO,其核心就是要拿到SQL查詢所對應(yīng)的每個列名及其數(shù)據(jù)類型。有了列名和數(shù)據(jù)類型,就能很容易寫一個方法來產(chǎn)生DTO了。

我們知道,在一般情況下,SQL查詢寫完之后,包括調(diào)用存儲過程和那些根據(jù)調(diào)用參數(shù)來動態(tài)拼接的SQL,雖然最終運行的SQL可能不盡相同,但是其查詢結(jié)果的字段部分都是相對固定的。

當(dāng)然,也有極少情況下會碰到字段都不確定的查詢,不過在這種極端情況下,即使手工也沒法寫DTO了,反倒是用Map更合適, 我們這里不做討論。

那么,怎么才能拿到列名和類型呢?

一種方案是分析SQL代碼中SELECT部分的字段,不過其局限性比較大:

對于拼接的SQL代碼,分析難度比較大

字段的類型也難以判斷

SELECT * ...; CALL statement 這樣常見的查詢方式分析起來難度也很大

上述方案對像Mybatis這種采用配置文件(xml)來寫SQL的方式,似乎有些可行性,我沒有具體試驗過,但估計面臨的困難不會少。

另一種方案是想辦法直接運行包含SQL的這些代碼:

我們知道JDBC執(zhí)行一個SQL查詢,會返回ResultSet對象,通過該對象中的方法getMetaData(),能夠得到這次查詢的一些元數(shù)據(jù):如列名稱,列類型,以及該列所在的表名等,這些信息就已經(jīng)足夠我們來產(chǎn)生需要的那個類了。

那么,怎么才能夠運行這些包含SQL的代碼呢?

對于那些固定的SQL語句還稍微好說點,我們拿到這個固定的SQL,調(diào)用JDBC就能拿到MetaData,然后就可以很容易的根據(jù)這些信息來生成DTO。但是,對于那些復(fù)雜的需要根據(jù)一系列參數(shù)來動態(tài)產(chǎn)生的SQL查詢,在參數(shù)設(shè)置好前是無法直接運行的,也就無法得到MetaData,得不到MetaData我們就無法生成DTO。

怎么辦?

前面已經(jīng)討論了,即便是動態(tài)SQL,無論輸入什么樣的參數(shù),雖然執(zhí)行的SQL語句可能不一樣,但是最終產(chǎn)生結(jié)果列卻是固定的。 我們當(dāng)前需要解決的問題不正是要獲取這些列信息嗎? 既然如此,那我們就構(gòu)造一系列默認(rèn)的參數(shù)值。這些參數(shù)并沒有實際用處,僅僅是為了讓我們正在編輯SQL代碼得以正常運行,以便拿到需要的MetaData,至于能否查詢到數(shù)據(jù)并不緊要。

通常我們編寫的SQL代碼,有2種存在形式:一是直接在Java代碼中, 另外一種是放在配置文件中。這里不討論哪種形式更好,以后我會多帶帶再找地方來討論。這里主要討論的是在Java代碼中拼接的SQL, 如何實現(xiàn)一個代碼生成器來自動生成這些DTO:

要全自動化的解決這個問題,我們先來看看這個代碼生成器所要面臨的一些挑戰(zhàn)及應(yīng)對的思路:

如何標(biāo)識一段需要生成DTO的SQL代碼

首先,我們需要標(biāo)識出這段代碼,以便于代碼生成器可以運行這段需要生成DTO代碼。而通常情況下,我們的數(shù)據(jù)接口都是方法級別的,因此我們可以通過對方法進(jìn)行注解,用注解來標(biāo)識這個方法要返回一個DTO對象是個不錯的選擇。

如何定義DTO的類名

一種很容易想到的方法就是通過SQL代碼所在的類名+方法名自動組合出一個名稱, 當(dāng)然有時為了靈活控制,應(yīng)該允許程序員指定一個名字。

如何執(zhí)行代碼

執(zhí)行代碼的關(guān)鍵是構(gòu)造一批能夠調(diào)用注解方法的合適參數(shù)。當(dāng)然首先需要對注解的方法進(jìn)行代碼分析,提取方法參數(shù)名及類型。代碼分析可以用類似JavaCC這樣的工具,或者一些語法分析器,這里不做細(xì)究。下面主要探討下默認(rèn)參數(shù)的構(gòu)造:

為了簡化問題,默認(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)為:類型[0], 構(gòu)造 int[] arg=new int[0];  
對象型參數(shù),默認(rèn)為:new 類型(), 例如:public Object find(User arg){...} 構(gòu)造 User arg=new User();  

當(dāng)然,對于一些簡單參數(shù)的情況下,上面構(gòu)造規(guī)則基本上都能夠奏效。 但是,對于有些參數(shù):比如參數(shù)是一個接口,或者是一個需要動態(tài)連接的表名,又或者是SQL拼接代碼的邏輯要求參數(shù)必須是某些特殊值等等,默認(rèn)構(gòu)造出的參數(shù)就會導(dǎo)致程序無法執(zhí)行。

但是,怎么才能夠讓我們的代碼生成器能夠繼續(xù)執(zhí)行下去呢? 好像確實沒有什么能自動處理的辦法,只好把這個問題交給程序員來處理了,讓程序員來幫助代碼生成器完成參數(shù)的初始化。

我們可以在注解上提供一個參數(shù), 該參數(shù)主要完成對默認(rèn)規(guī)則下無法初始化的參數(shù)進(jìn)行設(shè)置。 當(dāng)然,這個參數(shù)中的初始化代碼也可以覆蓋默認(rèn)規(guī)則,以便于我們在編輯階段就可以測試執(zhí)行不同的SQL流程。

如何生成DTO

經(jīng)過以上一系列的處理,我們終于能自動的把包含SQL查詢代碼的方法運行起來了。不過,現(xiàn)在我們還沒得到想要的MetaData,還無法生成DTO。

一種可能的方式是包裝一個JDBC,截獲本次方法調(diào)用時執(zhí)行的SQL查詢, 但面臨的問題是,如果方法中有多次查詢就比較麻煩了。

另一種方式依賴于框架的支持,可以截獲到方法的return語句,獲取其執(zhí)行的SQL語句, 有了SQL語句,生成DTO就沒有什么難度了。

如何修改代碼

為了盡量減少程序員的工作,我們的代碼生成器在生成完DTO后, 還需要將方法的返回值自動修改成這個DTO類。

如何處理SQL的變更

簡單的做法是:一旦有某個SQL代碼發(fā)生變化,就把所有的DTO都按照前面的方法重新生成一遍。 不過,很顯然當(dāng)查詢方法很多的時候,DTO代碼生成的過程將緩慢到難以忍受。

另外一種更合理的做法是:我們在生成DTO時增加一個指紋字段,其值可以用SQL代碼中所包含的信息來產(chǎn)生,例如:代碼長度+代碼的hashCode.代碼生成器在決定是否需要處理這個方法前,先計算該方法的指紋和存在于DTO里面的指紋進(jìn)行比較,如果相同就跳過,否則就認(rèn)為本方法的SQL發(fā)生了變更,需要更新DTO。

具體的實現(xiàn)

到此為止,基本上DTO代碼生成器的主要障礙都有了相應(yīng)的處理辦法。最后,我們用一個具體的實現(xiàn)來做個簡單示例。

這里需要引入2個項目:

monalisa-orm: https://github.com/11039850/monalisa-orm

這是一個功能強大且非常容易使用的ORM框架,通過@DB(jdbc_url,username,password)注解來引入數(shù)據(jù)庫。

monalisa-eclipse: https://github.com/11039850/monalisa-eclipse

這是一個相應(yīng)的Eclipse插件,它可以:

@DB注解的接口,在文件保存時 ,自動生成表的CRUD操作

@Select注解的方法,在文件保存時 ,自動生成DTO

很輕松的書寫多行字符串

插件安裝和設(shè)置可以參考: https://github.com/11039850/monalisa-orm/wiki/Code-Generator

下面是一個根據(jù)動態(tài)SQL自動生成DTO示例,完整的例子工程可以參考: https://github.com/11039850/monalisa-example

    package test.dao;
    
    public class UserBlogDao {
        //@Select 注解指示該方法需自動生成DTO
        //默認(rèn)類名: Result + 方法名, 默認(rèn)包名:數(shù)據(jù)訪問類的包名+"."+數(shù)據(jù)訪問類的名稱(小寫)
        //可選參數(shù):name 指定生成結(jié)果類的名稱,如果未指定該參數(shù),則采用默認(rèn)類名
        //可選參數(shù):build 初始化調(diào)用參數(shù)的Java片段代碼,替換默認(rèn)的參數(shù)構(gòu)造規(guī)則
        @Select(name="test.result.UserBlogs") 
    
        //!!! 保存后會自動修改該函數(shù)的返回值為: List -> List
        //第一次編寫時,由于結(jié)果類還不存在, 為了保證能夠編譯正常,
        //函數(shù)的返回值 和 查詢結(jié)果要用 泛值 替代, 保存后,插件會自動修改.
        //函數(shù)的返回值 和 查詢結(jié)果 泛值的對應(yīng)關(guān)系分三類如下:
        //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(); 
        } 
    }

上述代碼保存后,插件就會自動生成一個DTO類:test.result.UserBlogs, 并自動將方法修改成如下的聲明:

        public List  selectUserBlogs(int user_id){ 
            ...
            return q.getList(UserBlogs.class); 
        }

當(dāng)然,如果對selectUserBlogs方法做了任何的修改(包括只是加了一個空格),保存文件后,插件也會自動更新UserBlogs。

同時,為了方便我們調(diào)試,插件也會在Eclipse的控制臺窗口輸出類似下面的信息:

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

順便補充一下:

在Java代碼中書寫SQL,非常令人討厭的一件事情就是Java語言中字符串的連接問題。使得大段的SQL代碼中間要插很多的換行/轉(zhuǎn)義符號,寫起來很麻煩,看著也不舒服。monalisa-eclipse插件順便也解決了多行字符串的書寫問題。

例如:

    System.out.println(""/**~{
        SELECT * 
            FROM user
            WHERE name="zzg"
    }*/);

將會輸出:

    SELECT * 
        FROM user
        WHERE name="zzg"

當(dāng)然,為了快速書寫,可以在Eclipse中把多行字符串的語法設(shè)置為一個代碼模板。關(guān)于多行語法的更多細(xì)節(jié)可以參考: https://github.com/11039850/monalisa-orm/wiki/Multiple-line-syntax

到這里,動態(tài)SQL代碼自動生成DTO的思路和實現(xiàn)例子基本上就介紹完了, 歡迎大家提出各種有理無理的意見,一起討論、進(jìn)步,謝謝!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/64869.html

相關(guān)文章

  • 第三十章:SpringBoot使用MapStruct自動映射DTO

    摘要:商品類型實體恒宇少年碼云商品基本信息實體恒宇少年碼云接下來我們繼續(xù)創(chuàng)建相關(guān)的。注解是用于標(biāo)注接口抽象類是被自動映射的標(biāo)識,只有存在該注解才會將內(nèi)部的接口方法自動實現(xiàn)。 MapStruct是一種類型安全的bean映射類生成java注釋處理器。我們要做的就是定義一個映射器接口,聲明任何必需的映射方法。在編譯的過程中,MapStruct會生成此接口的實現(xiàn)。該實現(xiàn)使用純java方法調(diào)用的源和目...

    weakish 評論0 收藏0
  • [開源作品] skadmin 管理系統(tǒng)

    摘要:簡介項目基于的前后端分離的管理系統(tǒng),項目采用分模塊開發(fā)方式,權(quán)限控制采用,基于角色的訪問控制,支持?jǐn)?shù)據(jù)字典數(shù)據(jù)權(quán)限管理前端菜單支持動態(tài)路由,另外還有其他的功能模塊日志管理代碼生成器系統(tǒng)監(jiān)控云存儲管理系統(tǒng)工具等等。 簡介 項目基于 Spring Boot 2.1.0 、 Spring Data JPA、 Spring Security、Redis、Vue的前后端分離的管理系統(tǒng),項目采用分...

    codergarden 評論0 收藏0
  • Spring Boot QuickStart (5) - Spring Data JPA

    摘要:關(guān)聯(lián)關(guān)系的關(guān)聯(lián)關(guān)系定義上,感覺并不是很靈活,姿勢也比較難找。如,定義在關(guān)聯(lián)關(guān)系上的參數(shù)可以設(shè)置級聯(lián)的相關(guān)東西。因為序列化會涉及到實體類關(guān)聯(lián)對象的獲取,會觸發(fā)所有的關(guān)聯(lián)關(guān)系。 接(4) - Database 系列. Java Persistence API,可以理解就是 Java 一個持久化標(biāo)準(zhǔn)或規(guī)范,Spring Data JPA 是對它的實現(xiàn)。并且提供多個 JPA 廠商適配,如 Hi...

    sutaking 評論0 收藏0

發(fā)表評論

0條評論

weij

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<