欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JSqlParse完整介紹

 更新時(shí)間:2024年05月30日 10:48:13   作者:Interest1_wyt  
JSqlParse是一款很精簡(jiǎn)的sql解析工具,本文主要介紹了JSqlParse完整介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

1、jsqlparse介紹

JSqlParse是一款很精簡(jiǎn)的sql解析工具,它可以將常用的sql文本解析成具有層級(jí)結(jié)構(gòu)的“語(yǔ)法樹(shù)”,我們可以針對(duì)解析后的“樹(shù)節(jié)點(diǎn)(也即官網(wǎng)里說(shuō)的有層次結(jié)構(gòu)的java類(lèi))”進(jìn)行處理進(jìn)而生成符合我們要求的sql形式。

官網(wǎng)給的介紹很簡(jiǎn)潔:JSqlParser 解析 SQL 語(yǔ)句并將其轉(zhuǎn)換為 Java 類(lèi)的層次結(jié)構(gòu)。生成的層次結(jié)構(gòu)可以使用訪問(wèn)者模式進(jìn)行訪問(wèn)(官網(wǎng)地址:JSqlParser - Home)。

官網(wǎng)的介紹即是該中間件的全部,雖然介紹很短,但是其功能著實(shí)強(qiáng)悍。

2、jar包結(jié)構(gòu)介紹

這里我使用的是4.3版本,maven依賴(lài)如下:

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.3</version>
</dependency>

JSqlParse的總體代碼量不大,結(jié)構(gòu)也很簡(jiǎn)單,其項(xiàng)目整體結(jié)構(gòu)圖如下:

 可以看到其總共只有五個(gè)大的包,各個(gè)包的功能定義也很清晰:

  • expression:包含表達(dá)式相關(guān)的類(lèi)和接口,可以簡(jiǎn)單看做sql解析后的組成對(duì)象之一。如果需要對(duì)sql進(jìn)行一些更改變換,基本都會(huì)涉及到這個(gè)包。
  • parse:JSqlParse最核心的包,這個(gè)包里的類(lèi)實(shí)現(xiàn)了sql的解析,進(jìn)而我們才可以對(duì)解析后的sql(“java類(lèi)”)做各種自定義處理。雖然這個(gè)包是最核心的包,但如果純粹從使用角度上來(lái)說(shuō)可以不必太在意它,除非我們想深入了解sql解析的過(guò)程。
  • schema:可以理解為模式,即定義一些和數(shù)據(jù)中概念相對(duì)應(yīng)的類(lèi),如表Table、列Column等。
  • statement:sql語(yǔ)句也分很多種,如增刪改查等,這個(gè)包下就對(duì)應(yīng)各種解析后java類(lèi)所組成的sql語(yǔ)句,其內(nèi)部結(jié)構(gòu)如下:

util:JSqlParse解析中用到的工具類(lèi),基本也不用太在意,不過(guò)有個(gè)TablesNamesFinder類(lèi)則具有較強(qiáng)的參考價(jià)值。

其中該組件最厲害的地方是parse包的解析,即將sql解析成一組有血緣(或者成層級(jí)嵌套)的對(duì)象集,要了解這塊,需要對(duì)antlr有較深的理解才行。感興趣的可以專(zhuān)門(mén)去看一下。不過(guò)如果我們只是使用,就不需要專(zhuān)門(mén)了解語(yǔ)法的解析了,我們只需要知道如何對(duì)解析后的sql進(jìn)行修改即可。下面我會(huì)先講解大致大體的如何去做,最后一節(jié)再講解其中的一些原理。

3、使用介紹

sql語(yǔ)句的修改是通過(guò)實(shí)現(xiàn)對(duì)應(yīng)的訪問(wèn)者接口實(shí)現(xiàn)的,比如你想對(duì)from之后的table名稱(chēng)進(jìn)行處理,那么你只需要實(shí)現(xiàn) FromItemVisitor 接口并重寫(xiě) 訪問(wèn)Table的方法即可。如果你想對(duì)sql中的函數(shù)進(jìn)行處理,那么你只需要實(shí)現(xiàn)ExpressionVisitor接口并重寫(xiě)其中對(duì)應(yīng)的方法接口即可。

是不是很簡(jiǎn)單,不過(guò)這里有個(gè)問(wèn)題就是我們?nèi)绾伟盐覀冏远x的訪問(wèn)者傳給解析后的sql對(duì)象。因?yàn)榻馕龊蟮膕ql對(duì)象是具有層級(jí)的,我們要處理的對(duì)象很有可能在最內(nèi)層。如果你想自己遍歷解析后的sql對(duì)象,然后把訪問(wèn)者傳給特定的對(duì)象,這個(gè)方法雖然可行,但只能用于于不包含嵌套或者嵌套層次不深的sql語(yǔ)句,一旦包含嵌套語(yǔ)句或者sql語(yǔ)句很復(fù)雜,你很難一層層的去處理。

正確的做法是從sql解析后的第一層開(kāi)始,將每個(gè)遇到的相關(guān)訪問(wèn)者接口都實(shí)現(xiàn)一遍,這樣在獲得解析后的sql對(duì)象后,直接就可以將自定義訪問(wèn)者對(duì)象傳進(jìn)去,也不需要我們自己一層層去剝開(kāi)sql對(duì)象。我們只需要專(zhuān)注于自己需要的重寫(xiě)的訪問(wèn)者方法即可。展示下我實(shí)際中變更select語(yǔ)句用到的一些訪問(wèn)者接口,貼出來(lái)給大家看下:

StatementVisitor, SelectVisitor, SelectItemVisitor, FromItemVisitor, GroupByVisitor, ExpressionVisitor,ItemsListVisitor

這些訪問(wèn)者接口我也不是一次性全實(shí)現(xiàn)的,而是從最外層的StatementVisitor開(kāi)始,一點(diǎn)點(diǎn)加的,后續(xù)如果有需要可能還會(huì)再加,這個(gè)過(guò)程是一個(gè)比較繁瑣的逐漸深入和查漏補(bǔ)缺的過(guò)程,所以在sql語(yǔ)法替換時(shí)一定要保持謹(jǐn)慎。但這也給出一個(gè)建議,千萬(wàn)不要試圖追蹤各個(gè)模塊的迭代處理
情況,這樣很容易把你繞進(jìn)去,你只需關(guān)注當(dāng)前所在的模塊即可,其它的通過(guò)accpet交給其它對(duì)應(yīng)的visitor去處理。

下面以更改select類(lèi)型語(yǔ)句,將from之后table表名稱(chēng)從table1改為table2,和將max函數(shù)修改為min函數(shù)作為目標(biāo),我們來(lái)實(shí)現(xiàn)下這個(gè)需求:

首先是流程代碼,如下:

public class Main {
    
    public static void main(String[] args) throws Exception{
        //1、獲取原始sql輸入
        String sql = "select max(age) from table1";
        System.out.println("old sql:[{}]"+sql);
        //2、創(chuàng)建解析器
        CCJSqlParserManager mgr = new CCJSqlParserManager();
        //3、使用解析器解析sql生成具有層次結(jié)構(gòu)的java類(lèi)
        Statement stmt = mgr.parse(new StringReader(sql));
        //4、將自定義訪問(wèn)者傳入解析后的sql對(duì)象
        stmt.accept(new MyJSqlVisitor());
        //5、打印轉(zhuǎn)換后的sql語(yǔ)句
        System.out.println("new sql:[{}]" + stmt.toString());
    }
    
}

其次是最核心的訪問(wèn)者接口實(shí)現(xiàn)類(lèi),這里為了便于向大家展示sql修改的過(guò)程,我們一個(gè)個(gè)的添加接口:

首先是stmt.accept,這個(gè)對(duì)象接收的是一個(gè)StatementVisitor,所以我們?cè)谧远x的類(lèi)MyJSqlVisitor中先實(shí)現(xiàn)這個(gè)接口,因?yàn)槲覀円牡氖莝elect類(lèi)語(yǔ)句,所以我們可以找到對(duì)應(yīng)的visitor方法(至于為什么這個(gè)接口就是跟selet語(yǔ)句相關(guān),一個(gè)是根據(jù)方法名推斷,一個(gè)是debug查看,debug可以看到sql語(yǔ)句一層層的對(duì)象,再細(xì)就不啰嗦了,實(shí)戰(zhàn)個(gè)幾次就懂了)

public class MyJSqlVisitor implements StatementVisitor {
    
    @Override
    public void visit(Select select) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody != null) {
            selectBody.accept(this);
        }
    }

}

注意下,這里我只列出了一個(gè)實(shí)現(xiàn)的方法,是因?yàn)槠邢?,我只截取了?shí)現(xiàn)改動(dòng)的方法,后續(xù)也是只展示實(shí)現(xiàn)了變動(dòng)的代碼,接著可以看到selectBody也需要一個(gè)SelectVisitor類(lèi)型的訪問(wèn)者,所以我們?cè)費(fèi)yJSqlVisitor中添加實(shí)現(xiàn)該接口:

public class MyJSqlVisitor implements StatementVisitor, SelectVisitor {
    
    @Override
    public void visit(Select select) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody != null) {
            selectBody.accept(this);
        }
    }

    @Override
    public void visit(PlainSelect plainSelect) {
        /** 處理select字段 */
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        if (selectItems != null && selectItems.size() > 0) {
            selectItems.forEach(selectItem -> {
                selectItem.accept(this);
            });
        }

        /** 處理表名或子查詢(xún) */
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem!=null){
            fromItem.accept(this);
        }
    }

}

該接口對(duì)應(yīng)的visit方法中 selectItem和fromItem同時(shí)還需要SelectItemVisitor,F(xiàn)romItemVisitor兩種訪問(wèn)者,所以我們先來(lái)實(shí)現(xiàn)SelectItemVisitor這個(gè)接口:

public class MyJSqlVisitor implements StatementVisitor, SelectVisitor ,SelectItemVisitor {
    
    @Override
    public void visit(Select select) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody != null) {
            selectBody.accept(this);
        }
    }

    @Override
    public void visit(PlainSelect plainSelect) {
        /** 處理select字段 */
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        if (selectItems != null && selectItems.size() > 0) {
            selectItems.forEach(selectItem -> {
                selectItem.accept(this);
            });
        }

        /** 處理表名或子查詢(xún) */
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem!=null){
            fromItem.accept(this);
        }
    }

    // 這個(gè)方法我們并沒(méi)有考慮完全,比如select項(xiàng)目中可能有子查詢(xún)還有可能有case表達(dá)式,這些我們都沒(méi)考慮,這里只是先展示了一種思路。
    @Override
    public void visit(SelectExpressionItem selectExpressionItem) {
        if (Function.class.isInstance(selectExpressionItem.getExpression())) {
            Function function = (Function) selectExpressionItem.getExpression();
            function.accept(this);
        }
    }

}

可以看到function.accept還需要一個(gè)ExpressionVisitor,這里我們接著實(shí)現(xiàn)它:

public class MyJSqlVisitor implements StatementVisitor, SelectVisitor ,SelectItemVisitor, ExpressionVisitor {
    
    @Override
    public void visit(Select select) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody != null) {
            selectBody.accept(this);
        }
    }

    @Override
    public void visit(PlainSelect plainSelect) {
        /** 處理select字段 */
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        if (selectItems != null && selectItems.size() > 0) {
            selectItems.forEach(selectItem -> {
                selectItem.accept(this);
            });
        }

        /** 處理表名或子查詢(xún) */
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem!=null){
            fromItem.accept(this);
        }
    }

    // 這個(gè)方法我們并沒(méi)有考慮完全,比如select項(xiàng)目中可能有子查詢(xún)還有可能有case表達(dá)式,這些我們都沒(méi)考慮,這里只是先展示了一種思路。
    @Override
    public void visit(SelectExpressionItem selectExpressionItem) {
        if (Function.class.isInstance(selectExpressionItem.getExpression())) {
            Function function = (Function) selectExpressionItem.getExpression();
            function.accept(this);
        }
    }

    @Override
    public void visit(Function function) {
        if (function.getName().equalsIgnoreCase("max")){
            function.setName("min");
        }
    }

}

至此,max轉(zhuǎn)min已經(jīng)結(jié)束,我們?cè)倩剡^(guò)頭實(shí)現(xiàn)FromItemVisitor接口:

public class MyJSqlVisitor implements StatementVisitor, SelectVisitor ,SelectItemVisitor, ExpressionVisitor,FromItemVisitor {
    
    @Override
    public void visit(Select select) {
        SelectBody selectBody = select.getSelectBody();
        if (selectBody != null) {
            selectBody.accept(this);
        }
    }

    @Override
    public void visit(PlainSelect plainSelect) {
        /** 處理select字段 */
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        if (selectItems != null && selectItems.size() > 0) {
            selectItems.forEach(selectItem -> {
                selectItem.accept(this);
            });
        }

        /** 處理表名或子查詢(xún) */
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem!=null){
            fromItem.accept(this);
        }
    }

    // 這個(gè)方法我們并沒(méi)有考慮完全,比如select項(xiàng)目中可能有子查詢(xún)還有可能有case表達(dá)式,這些我們都沒(méi)考慮,這里只是先展示了一種思路。
    @Override
    public void visit(SelectExpressionItem selectExpressionItem) {
        if (Function.class.isInstance(selectExpressionItem.getExpression())) {
            Function function = (Function) selectExpressionItem.getExpression();
            function.accept(this);
        }
    }

    // 實(shí)現(xiàn)將max函數(shù)轉(zhuǎn)為min函數(shù)
    @Override
    public void visit(Function function) {
        if (function.getName().equalsIgnoreCase("max")){
            function.setName("min");
        }
    }

    //實(shí)現(xiàn)表名稱(chēng)的更換
    @Override
    public void visit(Table table) {
        if (table.getName().equalsIgnoreCase("table1")){
            table.setName("table2");
        }
    }

}

至此,我們的兩個(gè)修改目標(biāo)已經(jīng)達(dá)成,運(yùn)行main看下效果:

old sql:[{}]select max(age) from table1
new sql:[{}]SELECT min(age) FROM table2
 Process finished with exit code 0

可以看到我們的目的實(shí)現(xiàn)了,不過(guò)這里請(qǐng)留意我們并沒(méi)有考慮子查詢(xún)等其它情況,這個(gè)demo只是展示一種修改思路,工作中具體的操作要考慮的比這細(xì)致的多。

使用建議:

1)一個(gè)個(gè)的添加接口,遇到什么類(lèi)型的訪問(wèn)者,加什么類(lèi)型的實(shí)現(xiàn)接口,防止一次性加太多忘記實(shí)現(xiàn)邏輯。

2)不要試圖追蹤各個(gè)sql對(duì)象的迭代處理情況,這樣很容易把你繞進(jìn)去,你只需關(guān)注當(dāng)前所在的方法模塊即可,其它的通過(guò)accpet交給其它對(duì)應(yīng)的visitor去處理即可。

3)不要試圖一次性實(shí)現(xiàn)所有的訪問(wèn)者接口,根據(jù)需要進(jìn)行實(shí)現(xiàn)

4)sql語(yǔ)法樹(shù)具有很強(qiáng)的層次性,當(dāng)被訪問(wèn)者在進(jìn)行處理時(shí),要考慮到自己的子元素是不是也要進(jìn)行迭代處理,如果需要的話,那么就調(diào)用對(duì)應(yīng)子元素的accpect方法,并將相關(guān)訪問(wèn)者傳遞進(jìn)去

5)如果沒(méi)有使用容器技術(shù),所有的訪問(wèn)者接口盡量放在一個(gè)類(lèi)中實(shí)現(xiàn),這樣當(dāng)有accept需要visitor對(duì)象的時(shí)候直接傳this就行。(我一開(kāi)始沒(méi)有用容器管理bean,每個(gè)visitor接口我都單獨(dú)創(chuàng)建一個(gè)實(shí)現(xiàn)類(lèi),最后因?yàn)槭褂貌坏剑斐傻L問(wèn)時(shí)棧溢出錯(cuò)誤)

4、核心原理介紹

這塊只是展示sql迭代訪問(wèn)修改的原理,并不涉及將sql文本解析為對(duì)象類(lèi)的原理。好了,進(jìn)入正文。

要想理解sql迭代修改的原理,其實(shí)只要了解訪問(wèn)者模式多態(tài)這兩個(gè)知識(shí)點(diǎn)就行。如果不了解的可以先去查看對(duì)應(yīng)的知識(shí)點(diǎn),然后再看下源碼仔細(xì)體會(huì)下。下面我會(huì)簡(jiǎn)單介紹下,在前文我們也提過(guò),要想修改sql,只需要實(shí)現(xiàn)對(duì)應(yīng)的訪問(wèn)接口即可,然后將訪問(wèn)者傳入被訪問(wèn)的sql對(duì)象中。

在JSqlParse中,將解析后的sql對(duì)象看做被訪問(wèn)者,我們自定義的visitor則看做訪問(wèn)者。該組件同時(shí)將各類(lèi)被訪問(wèn)者和訪問(wèn)者都抽象出了接口,我們代碼編輯時(shí)通過(guò)接口確定大體的執(zhí)行流程,在具體的代碼運(yùn)行階段,就會(huì)通過(guò)多態(tài)尋找對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)。就拿demo中的statement來(lái)說(shuō),它是一個(gè)接口,但是運(yùn)行的時(shí)候就會(huì)根據(jù)sql情況定位到具體的實(shí)現(xiàn)類(lèi),我們demo中對(duì)應(yīng)的具體實(shí)現(xiàn)類(lèi)就是select對(duì)象,此時(shí)進(jìn)入該對(duì)象查看具體的accept方法:

可以看到被訪問(wèn)者調(diào)用的還是訪問(wèn)者的visit方法,也就是我們對(duì)應(yīng)的重寫(xiě)方法。以此類(lèi)推,剩下的各個(gè)層級(jí)處理也是通過(guò)重復(fù)這個(gè)過(guò)程,所以想理解這個(gè)處理過(guò)程,一定要理解訪問(wèn)者模式

到此這篇關(guān)于JSqlParse完整介紹的文章就介紹到這了,更多相關(guān)JSqlParse使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Maven倉(cāng)庫(kù)的具體使用(本地倉(cāng)庫(kù)+遠(yuǎn)程倉(cāng)庫(kù))

    Maven倉(cāng)庫(kù)的具體使用(本地倉(cāng)庫(kù)+遠(yuǎn)程倉(cāng)庫(kù))

    Maven 在某個(gè)統(tǒng)一的位置存儲(chǔ)所有項(xiàng)目的構(gòu)件,這個(gè)統(tǒng)一的位置,我們就稱(chēng)之為倉(cāng)庫(kù),本文主要介紹了Maven倉(cāng)庫(kù)的具體使用(本地倉(cāng)庫(kù)+遠(yuǎn)程倉(cāng)庫(kù)),感興趣的可以了解一下
    2023-11-11
  • 淺談Java8 判空新寫(xiě)法

    淺談Java8 判空新寫(xiě)法

    在開(kāi)發(fā)過(guò)程中很多時(shí)候會(huì)遇到判空校驗(yàn),如果不做判空校驗(yàn)則會(huì)產(chǎn)生NullPointerException異常,本文就來(lái)介紹一下Java8 判空新寫(xiě)法,感興趣的可以了解一下
    2021-09-09
  • 關(guān)于@SpringBootApplication詳解

    關(guān)于@SpringBootApplication詳解

    這篇文章主要介紹了關(guān)于@SpringBootApplication的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Spring?Boot實(shí)現(xiàn)JWT?token自動(dòng)續(xù)期的實(shí)現(xiàn)

    Spring?Boot實(shí)現(xiàn)JWT?token自動(dòng)續(xù)期的實(shí)現(xiàn)

    本文主要介紹了Spring?Boot實(shí)現(xiàn)JWT?token自動(dòng)續(xù)期,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • 聊聊maven的pom.xml中的exclusions標(biāo)簽的作用

    聊聊maven的pom.xml中的exclusions標(biāo)簽的作用

    這篇文章主要介紹了maven的pom.xml中的exclusions標(biāo)簽的作用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • MybatisPlus中@TableField注解的使用詳解

    MybatisPlus中@TableField注解的使用詳解

    這篇文章主要介紹了MybatisPlus中@TableField注解的使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • java實(shí)戰(zhàn)小技巧之優(yōu)雅的實(shí)現(xiàn)字符串拼接

    java實(shí)戰(zhàn)小技巧之優(yōu)雅的實(shí)現(xiàn)字符串拼接

    字符串拼接是我們?cè)贘ava代碼中比較經(jīng)常要做的事情,就是把多個(gè)字符串拼接到一起,這篇文章主要給大家介紹了關(guān)于java實(shí)戰(zhàn)小技巧之優(yōu)雅的實(shí)現(xiàn)字符串拼接的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)

    Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)

    本文主要介紹了Spring MVC登錄注冊(cè)以及轉(zhuǎn)換json數(shù)據(jù)的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧
    2017-04-04
  • java結(jié)合keytool如何實(shí)現(xiàn)非對(duì)稱(chēng)簽名和驗(yàn)證詳解

    java結(jié)合keytool如何實(shí)現(xiàn)非對(duì)稱(chēng)簽名和驗(yàn)證詳解

    這篇文章主要給大家介紹了關(guān)于java結(jié)合keytool如何實(shí)現(xiàn)非對(duì)稱(chēng)簽名和驗(yàn)證的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式

    spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式

    這篇文章主要介紹了spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12

最新評(píng)論