像寫SQL一樣編寫Java數據應用-TinySqlDsl
前言
話說企業應用,一般離不開數據庫。要做數據庫,可以有N種方案,比如:直接采用JDBC層自己封裝下使用的,采用一些框架的,如:iBatis,Hiberate,Spring JDBC Template等等(這個太多了,因此不一一列舉)的,這些方案也都在各自的領域展示了自己的特點,解決了相當部分的技術問題,并取得了相當好的應用效果。
但是不管是哪種方案,其優點和缺點往往也是連在一起的,究其原因是因為SQL和Java編程之間是割裂的,如果封裝得不到位,做Java的人太難使用;如果封裝得太多,在做一些用復雜SQL的時候又非常麻煩。比如:Hibernate就采用了封裝HQL的方式來解決這方面的問題。iBatis對于SQL支持比較好,但是又會有一些割裂感,同時在解決時還要引入動態SQL來解決需要根據一些運行時條件來處理的問題,一定程度上又增加了使用的復雜度。
那么問題就來了,有沒有更好的方式來解決數據庫應用開發過程中的問題呢?究其根本原因是要如何解決數據庫開發中的SQL與Java代碼之間的割裂問題,如果能把這個問題解決掉,理論上會有一個不錯的解。
我們知道SQL實際是是一種數據為領域的DSL語言,如果我們能直接在Java中編寫SQL,然后執行結果就可以直接返回Java對象,這個問題不就有了良好的解決方案么?
TinySqlDsl解決方案
實際上這方面已經有一些現成的解決方案,但是有的不是開源的,有的支持的還不是非常到位,因此悠然就決定嘗試著寫一下,寫了半天時間看了看效果,詳見RESTful風格的支持實踐一文,內部討論了一下,感覺還不錯,于是正式決定正式花時間來編寫一個TinySqlDsl,當然實際編寫的時候,還是有許多的問題點的,以至于最終的風格與上面的文章還有一些不一致,當然這也是正常的,容易理解的,否則那什么也太神了。
我們常見的SQL語句有Select、Insert、Update、Delete,因此我們的方案中也實現了這幾個語句的編寫方式。
首先來看看看TinySqlDsl版的Dao是怎么寫的。
第一步:定義POJO
public class Custom { private String id; private String name; private int age; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
第二步:定義表結構定義文件
public class CustomTable extends Table { public static final CustomTable CUSTOM = new CustomTable(); public final Column ID = new Column(this, "id"); public final Column NAME = new Column(this, "name"); public final Column AGE = new Column(this, "age"); private CustomTable() { super("custom"); } }
第三步:編寫DAO類
public class CustomDao { private DslSession dslSession; public DslSession getDslSession() { return dslSession; } public void setDslSession(DslSession dslSession) { this.dslSession = dslSession; } public void insertCustom(Custom custom) { dslSession.execute( insertInto(CUSTOM).values( CUSTOM.ID.value(custom.getId()), CUSTOM.NAME.value(custom.getName()), CUSTOM.AGE.value(custom.getAge()) ) ); } public void updateCustom(Custom custom) { dslSession.execute( update(CUSTOM).set( CUSTOM.NAME.value(custom.getName()), CUSTOM.AGE.value(custom.getAge())).where( CUSTOM.ID.eq(custom.getId()) ) ); } public void deleteCustom(String id) { dslSession.execute( delete(CUSTOM).where( CUSTOM.ID.eq(id) ) ); } public Custom getCustomById(String id) { return dslSession.fetchOneResult( selectFrom(CUSTOM).where( CUSTOM.ID.eq(id) ) , Custom.class); } public List<Custom> queryCustom(Custom custom) { return dslSession.fetchList( selectFrom(CUSTOM).where( and( CUSTOM.ID.eq(custom.getId()), CUSTOM.NAME.equal(custom.getName()), CUSTOM.AGE.equal(custom.getAge()) ) ) , Custom.class); } }看了上面的示例,會不會感覺有點奇怪,怎么可以這么寫?呵呵,先別著急了解實際的實現機理,我們先品味一下這種DSL風格的數據庫編寫方式,嗯嗯,具體的來說就是像寫SQL一樣的方式來寫SQL。
代碼說明
每個數據表都要有兩個類進行映射,一個是POJO類,這個大家都非常熟悉就不再花時間進行說明了,用于構建Dao代碼的時候使用。另一個是表結構,用于在Java中定義數據庫的表結構。
public class CustomTable extends Table { public static final CustomTable CUSTOM = new CustomTable(); public final Column ID = new Column(this, "id"); public final Column NAME = new Column(this, "name"); public final Column AGE = new Column(this, "age"); private CustomTable() { super("custom"); } }這個類主要由如下幾部分組成:
CustomTable對應于一個表結構類型,它繼承自Table類。
構造函數,中的super("custom")使之與數據庫的表名進行映射。
public static final CustomTable CUSTOM = new CustomTable();這句定義了一個常量CUSTOM,對應于具有的表,它的用得中在DSL語法用要用到表的時候使用。
這個類里定義了3個public成員變量,這些成員變量和具體的字段數相對應,表里有幾個字段,這里就定義幾個字段,這個實例化自Column。
OK,這樣表結構的定義就做好了。
正因為有了上面的定義,才可以在Dao中用Java代碼像SQL一樣的編寫程序,但是這些語句是怎么才能執行出結果的呢?這就要看DslSession的了。
DslSesssion
DslSession是與數據庫打交道的類,說白了,它就是一個SQL執行器。
public interface DslSession { /** * 執行Insert語句關返回 * * @param insert * @return */ int execute(Insert insert); /** * 執行更新語句 * * @param update * @return */ int execute(Update update); /** * 執行刪除語句 * * @param delete * @return */ int execute(Delete delete); /** * 返回一個結果,既然是有多個結果也只返回第一個結果 * * @param select * @param requiredType * @param <T> * @return */ <T> T fetchOneResult(Select select, Class<T> requiredType); /** * 把所有的結果變成一個對象數組返回 * * @param select * @param requiredType * @param <T> * @return */ <T> T[] fetchArray(Select select, Class<T> requiredType); /** * 把所有的結果變成一個對象列表返回 * * @param select * @param requiredType * @param <T> * @return */ <T> List<T> fetchList(Select select, Class<T> requiredType); /** * 返回一個結果,既然是有多個結果也只返回第一個結果 * * @param complexSelect * @param requiredType * @param <T> * @return */ <T> T fetchOneResult(ComplexSelect complexSelect, Class<T> requiredType); /** * 把所有的結果變成一個對象數組返回 * * @param complexSelect * @param requiredType * @param <T> * @return */ <T> T[] fetchArray(ComplexSelect complexSelect, Class<T> requiredType); /** * 把所有的結果變成一個對象列表返回 * * @param complexSelect * @param requiredType * @param <T> * @return */ <T> List<T> fetchList(ComplexSelect complexSelect, Class<T> requiredType); }它的方法也比較簡單,主要功能就是執行這幾個語句。正是由于把復雜的SQL都封裝到了Insert、Select、Update、Delete當中,因此這個執行器的接口方法反而是非常的簡單,正因為它太簡單了,因此根本就不需要介紹。僅僅要說明的是,當Select的時候,需要指定返回的類型,以便于告訴DslSession要返回的類型是什么。
Q&A
Q:是不是支持復雜的SQL?
A:必須支持,不管是Union,子查詢,各種連接都可以支持
Q:是不是支持分頁?
A:必須支持,不管是拼SQL語句分頁的還是SQL默認就支持分頁的,都可以支持
Q:你這個SQL條件一路寫下來,是不是需要所有的條件都必須存在?
A:不用,對于沒有給值的條件,框架會自動忽略此條件,所以你只要寫一個大而全的就可以了。
Q:是不是支持數據庫中的函數?
A:必須支持,所有的函數都可以使用,只是如果寫了與某種數據庫相關的函數,跨數據庫時將不再有兼容性。
Q:是不是支持多表關聯查詢?
A:必須支持,不管是多表聯合查詢還是子查詢啥的,全都支持。
Q:有啥不支持的不?
好像沒有啥不支持的,只有寫得漂亮不漂亮的,沒有支持不支持的。由于支持自已編寫SQL片斷,因此理論上你可以用SQL片斷完成所有的事情,只是看起來不夠漂亮而已。
應用實踐
支持類編寫
使用Tiny元數據開發
如果使用Tiny元數據管理數據表,那么只要在工具中如下操作,即可自動生成POJO、表定義、及Dao層代碼實現:
也就是只要選中表定義文件,選擇右鍵->TinyStudio->生成DSL JAVA類,就可以自動生成Dao層的所有代碼,如果需要可以對生成的類進行修改或擴展,但是一般情況下都足夠使用了。
自行編寫或生成
如果沒有使用Tiny的元數據,那么可以自己寫個工具類來生成這幾個類,也可以手工編寫,也可以分分鐘編寫出來。
DAO編寫注意事項
import static org.tinygroup.tinysqldsl.CustomTable.CUSTOM; import static org.tinygroup.tinysqldsl.Delete.delete; import static org.tinygroup.tinysqldsl.Insert.insertInto; import static org.tinygroup.tinysqldsl.Select.selectFrom; import static org.tinygroup.tinysqldsl.base.StatementSqlBuilder.and; import static org.tinygroup.tinysqldsl.Update.update;
優缺點對比
任意一個方案都有它的優點,也有它的缺點,TinySqlDsl也不例外,這里簡單的分析一下,如果不全面,請同學們下面補充,先謝謝了。
優點
- 熟悉SQL的同學,上手非常方便,可以說熟悉SQL的同學,可以非常快的上手,甚至不會Java都可以快速編寫
- 即時提示效果非常好,所有的IDE都提供的語法提示,使得編寫SQL時,對于表結構不必再記得一清二楚,第一編寫速度快許多,第二不用擔心拼寫錯誤而花費大量的調試時間
- SQL的構建和Java的處理一體化完成,開發過程不必兩個部分先分開再分離
- 完美的解決動態SQL方面的問題,不需要復雜的配置,不需要復雜的處理,一切渾然天成
- 像寫SQL一樣寫Java數據庫業務代碼
缺點
- 這種方式畢竟和寫SQL還是有一點區別,需要花一點時間熟悉
- 更多的親們在下面補充
總結
目前,我們內部進行了試用,整體運行效果良好,后面準備主力推這種方式。
關心代碼的同學,可以查看下面的URL:http://git.oschina.net/tinyframework/tiny/tree/master/db/org.tinygroup.tinysqldsl
來自:http://my.oschina.net/tinyframework/blog/410497