像寫SQL一樣編寫Java數據應用-TinySqlDsl

bgn4 9年前發布 | 15K 次閱讀 SQL 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;



這里用到一個技巧,就是通過靜態引入這些要用到的語句或表定義,這樣才可以方便的編寫DSL格式的語句。

優缺點對比

任意一個方案都有它的優點,也有它的缺點,TinySqlDsl也不例外,這里簡單的分析一下,如果不全面,請同學們下面補充,先謝謝了。

優點

  1. 熟悉SQL的同學,上手非常方便,可以說熟悉SQL的同學,可以非常快的上手,甚至不會Java都可以快速編寫
  2. 即時提示效果非常好,所有的IDE都提供的語法提示,使得編寫SQL時,對于表結構不必再記得一清二楚,第一編寫速度快許多,第二不用擔心拼寫錯誤而花費大量的調試時間
  3. SQL的構建和Java的處理一體化完成,開發過程不必兩個部分先分開再分離
  4. 完美的解決動態SQL方面的問題,不需要復雜的配置,不需要復雜的處理,一切渾然天成
  5. 像寫SQL一樣寫Java數據庫業務代碼

缺點

  1. 這種方式畢竟和寫SQL還是有一點區別,需要花一點時間熟悉
  2. 更多的親們在下面補充

總結

目前,我們內部進行了試用,整體運行效果良好,后面準備主力推這種方式。

關心代碼的同學,可以查看下面的URL:http://git.oschina.net/tinyframework/tiny/tree/master/db/org.tinygroup.tinysqldsl

來自:http://my.oschina.net/tinyframework/blog/410497

 本文由用戶 bgn4 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!