Mybatis中SqlMapper配置的擴展與應用(3)

来源:http://www.cnblogs.com/linjisong/archive/2016/11/19/6080834.html
-Advertisement-
Play Games

隔了兩周,首先回顧一下,在Mybatis中的SqlMapper配置文件中引入的幾個擴展機制: 1.引入SQL配置函數,簡化配置、屏蔽DB底層差異性 2.引入自定義命名空間,允許自定義語句級元素、腳本級元素 3.引入表達式配置,擴充SqlMapper配置的表達能力 前面兩條已經舉過例子,現在來看看怎麼 ...


隔了兩周,首先回顧一下,在Mybatis中的SqlMapper配置文件中引入的幾個擴展機制:

1.引入SQL配置函數,簡化配置、屏蔽DB底層差異性
2.引入自定義命名空間,允許自定義語句級元素、腳本級元素
3.引入表達式配置,擴充SqlMapper配置的表達能力

前面兩條已經舉過例子,現在來看看怎麼使用表達式配置。說到表達式語言,最為富麗堂皇的自然就是OGNL,但這也正是Mybatis內部訪問數據的固有方式,所以也輪不到我們在這裡來擴充了(事實上Mybatis的參數設置並不能使用完全的OGNL)。那麼,除了OGNL,還有哪些表達式語言呢?別忘了,我們的前提是Spring環境,自然,SpEL表達式也就走入我們的視野,因此這篇文章就重點記錄在SqlMapper中使用SpEL表達式

四、在Mybatis中的SqlMapper使用SpEL表達式

1.SpEL工具類

SpEL就是Spring提供的EL表達式,雖然到Spring3才開始推出,但已經是Spring的一個基礎核心模塊了,地位已經差不多等同於IoC和AOP了。SpEL和OGNL類似,也有表達式、上下文環境、root對象等概念,但和OGNL不同的是,SpEL還提供了訪問Spring中bean的能力——這是非常強悍的,試問一個Spring應用有多少類不是Spring管理的呢?具體的SpEL語法細節可以參考Spring的官方文檔。
SpEL目前主要應用於Spring的配置,使用起來非常方便,但是在Java類中使用則比較繁瑣,稍微實用一點的例子都需要創建解析器實例、創建執行環境、解析表達式、對錶達式求值等步驟,如果需要訪問Spring的Bean,還要設置BeanFactoryResolver等,因此,為了簡化SpEL在Java中的應用,我編寫了一個SpEL的幫助類:

這個工具類分成四個部分:

  1. 實現ApplicationContextAware介面,註入ApplicationContext(BeanFactory)對象
  2. 表達式求值方法
    • 對錶達式簡單求值(還可指定返回的目標類型)
    • 指定root對象,對錶達式求值(還可指定返回的目標類型)
    • 指定root對象和其它變數,對錶達式求值(還可指定返回的目標類型)
  3. 表達式設置方法
    • 設置表達式的值
    • 指定root對象,設置表達式的值
    • 指定root對象和其它變數,設置表達式的值
  4. 變數管理方法
    • 添加變數
    • 移除變數

此外,還內置了一個保護變數Tool。
編寫一個測試類驗證一下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
    "classpath:applicationContext.xml"  
})
@Component  // 該測試類本身作為一個Spring管理的bean,便於後面的測試
public class SpringHelpTest {
    
    public String getBeanValue(String arg){//bean的一個方法
        return "beanValue:"+arg;
    }

    @Test
    public void testSpelHelp(){
        // 準備root對象 {key1 : 'root-value1', key2 : 'root-value2'}
        Root root = new Root("root-value1", "root-value2");
        // 準備一般變數
        Map<String, Object> vars = new HashMap<String, Object>();
        vars.put("var1", "value1");
        vars.put("var2", "value2");
        // 直接計算簡單表達式
        Object rs = SpringHelp.evaluate("1+2");
        Assert.assertEquals(3, rs);
        // 按指定類型計算簡單表達式
        rs = SpringHelp.evaluate("1+2", String.class);
        Assert.assertEquals("3", rs);
        // 訪問root對象的屬性
        rs = SpringHelp.evaluate(root, "key1");
        Assert.assertEquals("root-value1", rs);
        // 訪問一般變數
        rs = SpringHelp.evaluate(root, "#var2", vars);
        Assert.assertEquals("value2", rs);
        // 訪問root對象
        rs = SpringHelp.evaluate(root, "#root", vars);
        Assert.assertTrue(rs == root);
        // 訪問Spring管理的bean,同時傳入的參數又是root對象的屬性
        rs = SpringHelp.evaluate(root, "@springHelpTest.getBeanValue(key2)", vars);
        Assert.assertEquals("beanValue:root-value2", rs);
        // 設置root對象的屬性
        SpringHelp.setValue(root, "key1", "new-root-value1");
        rs = SpringHelp.evaluate(root, "key1");
        Assert.assertEquals("new-root-value1", rs);
        //訪問工具類,其中Tool.DATE.getDate()的作用是獲取當前日期
        rs = SpringHelp.evaluate("#Tool.DATE.getDate()");
        Assert.assertEquals(Tool.DATE.getDate(), rs);
    }
    
    public class Root{
        String key1;
        String key2;
        Root(String key1, String key2){
            this.key1 = key1;
            this.key2 = key2;
        }
        // 省略getter/setter方法
    }
}

有了這個靜態幫助類,在Java中使用SpEL就方便很多了。

2.編寫表達式處理器

利用SpEL幫助類,編寫表達式處理器IExpressionHandler的實現,具體邏輯參看代碼中的註釋

public class SpelExpressionHandler implements IExpressionHandler {
    
    /**
     * 直接返回true,也就是說不做進一步判斷,支持所有的${(exp)}、#{(exp)}內的表達式
     * 由於支持所有表達式,實際上起到了一種攔截作用,所以需要註意,註冊該實現時必須最低優先順序
     */
    @Override
    public boolean isSupport(String expression) {
        return true;
    }

    /**
     * 對SqlMapper配置中的表達式求值
     */
    @Override
    public Object eval(String expression, Object parameter, String databaseId) {
        /**
         * 如果以spel:為首碼,則將mybatis包裝後的參數、資料庫id以及表達式自身一起封裝一個新的root對象
         * 因此在exp表達式中可以通過params.paramName、databaseId等形式訪問
         */
        if(expression.toLowerCase().startsWith("spel:")){
            expression = expression.substring(5);
            Root root = new Root(parameter, databaseId, expression);
            return SpringHelp.evaluate(root, expression);
        }
        /**
         * 否則將databaseId作為一個特殊名稱的變數
         * 因此在exp表達式中可以通過paramName、#databaseId等形式訪問
         */
        else{
            Map<String, Object> vars = new HashMap<String, Object>();
            vars.put("databaseId", databaseId);
            return SpringHelp.evaluate(parameter, expression, vars);
        }
    }
    
    public class Root {

        private final Object params;
        private final String databaseId;
        private final String expression;

        public Root(Object params, String databaseId, String expression) {
            this.params = params;
            this.databaseId = databaseId;
            this.expression = expression;
        }

        // 省略getter/setter方法
    }
}

3.註冊表達式處理器

如上面的註釋,註冊的時候需要註意一點,優先順序要最低,以避免所有表達式都被攔截,導致其它的處理器不生效。
保證優先順序最低,有一種方法,就是實現Spring中的Order介面,並且將該實現類的order值設置為最大,然後按Order排序;另外一種方法,就是乾脆另起爐竈,單獨一個屬性保存預設處理器,只有其它處理器都不支持的時候才使用預設處理器,請看下麵的代碼:

/**
 * 表達式處理器
 */
private static final Set<IExpressionHandler> expressions = new LinkedHashSet<IExpressionHandler>();
/**
 * 預設表達式處理器
 */
private static final IExpressionHandler defaultExpressionHandler = new SpelExpressionHandler();
/**
 * 獲取表達式處理器
 * @param node
 * @return
 */
public static IExpressionHandler getExpressionHandler(String expression){
    for(IExpressionHandler handler : expressions){
        if(handler.isSupport(expression)){
            return handler;
        }
    }
    return defaultExpressionHandler;
}

4.修改SqlMapper中配置

<?xml version="1.0" encoding="UTF-8" ?>
<mapper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://dysd.org/schema/sqlmapper"
    xmlns:e="http://dysd.org/schema/sqlmapper-extend"
    xsi:schemaLocation="http://dysd.org/schema/sqlmapper http://dysd.org/schema/sqlmapper.xsd
        http://dysd.org/schema/sqlmapper-extend http://dysd.org/schema/sqlmapper-extend.xsd"
    namespace="org.dysd.dao.mybatis.mapper.IExampleDao">
    
    <select id="selectString" resultType="string">
        select PARAM_NAME, ${(@spelBean.param(paramName))} AS TEST_SPEL
          from BF_PARAM_ENUM_DEF
         where PARAM_NAME $like{#{(spel:@spelBean.root(#root,params.paramName)), jdbcType=VARCHAR}}
         order by SEQNO
    </select>
</mapper>

5.編寫配置中的bean

@Component("spelBean")
public class SpelBean {

    public String param(String paramName){
        // 測試的是${()},所以返回結果中添加單引號
        return "'PARAM-"+paramName+"'";
    }
    
    public String root(SpelExpressionHandler.Root root,String paramName){
        // 測試spel:為首碼的表達式,所以可以直接訪問SpelExpressionHandler.Root對象
        return "ROOT-"+root.getDatabaseId()+"-"+paramName;
    }
}

6.編寫Dao介面

@Repository
public interface IExampleDao {
    
    public String selectString(@Param("paramName")String paramName);
}

7.編寫JUnit測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
    "classpath:spring/applicationContext.xml"   
})
@Service
public class ExampleDaoTest{

    @Resource
    private IExampleDao dao;
    
    @Test
    public void testSelectString(){
        try {
            String a = dao.selectString("DISPLAY_AREA");
            Assert.assertEquals("顯示區域", a);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

8.執行測試

20161119 19:00:44,298 [main]-[DEBUG] ==>  Preparing: select PARAM_NAME, 'PARAM-DISPLAY_AREA' AS TEST_SPEL from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE CONCAT('%',?,'%') order by SEQNO 
20161119 19:00:48,001 [main]-[DEBUG] ==> Parameters: ROOT-MySQL-DISPLAY_AREA(String)

可以看到,無論是${(exp)}還是#{(exp)},其中的exp都已經得到正確的解析了。

在SqlMapper中可以調用Spring的Bean,大大豐富了SqlMapper的表達能力,但是對於${(exp)}這種情形,由於是字元串的簡單替換,也存在SQL註入的風險,因此一般只使用#{(exp)}。

題外話:
1.SqlMapper的擴展與應用系列算是暫告一段落,有朋友希望我能提供實際的案例,我利用這兩周的業餘時間整理了一下,在GitHub和OSChina同步上傳了這個項目,有興趣的朋友可以看一下,也希望可以多提一點建議給我。因為是maven項目,希望實際運行的朋友最好搭建一個nexus私服,然後git下載,導入至Eclipse中,修改資料庫配置即可。
具體地址:
GitHub:https://github.com/linjisong/dysd
OSChina:https://git.oschina.net/linjisong/dysd
2.在博客園中首次使用Markdown,好多地方還不熟悉,比如代碼摺疊,但也算是一種新的嘗試。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #! usr/bin/env python#!-*-conding:utf-8-*-#第一步:定義變數 name = 'python' passwd = 'password'#第二步:寫while迴圈 定義count=0#第三步:判斷count=3則列印print("Account Be Locke ...
  • 分散式系統關鍵點 分散式系統(distributed system)是建立在網路之上的軟體系統。正是因為軟體的特性,所以分散式系統具有高度的內聚性和透明性。因此,網路和分散式系統之間的區別更多的在於高層軟體(特別是操作系統),而不是硬體。內聚性是指每一個資料庫分佈節點高度自治,有本地的資料庫管理系統 ...
  • 轉載:http://www.jb51.net/article/40193.htm JS里設定延時: 使用SetInterval和設定延時函數setTimeout 很類似。setTimeout 運用在延遲一段時間,再進行某項操作。 setTimeout("function",time) 設置一個超時對 ...
  • 分組後,統計記錄條數: SELECT num,count(*) AS counts from test_a GROUP BY num; 查詢結果如下: 對num去重後的數量的統計: SELECT count(t.counts) FROM ( SELECT num,count(*) AS counts ...
  • 協議與相容性 spider使用java語言開發,使用Spring作為IoC容器,採用TCP/IP協議,在此基礎上,結合SaaS系統模式的特性進行針對性和重點設計,以更加靈活和高效的滿足多租戶系統、高可用、分散式部署的要求。 採用JSON作為序列化機制,後續版本可能會考慮支持protobuf(java ...
  • 1、局部變數 運行結果: 0 1 2 3 4 4 i是for語句裡面的局部變數。但在python裡面,在同一方法體內,定義了一個局部變數,該變數的作用域是定義行開始至該方法體結束。 在其他編程語言,“print i”那一句是有誤的,因為i沒有定義 例1: 結果: True 例2: 最後一行有誤,因為 ...
  • 一、怎樣判斷對象是否可以轉換?可以使用instanceof運算符判斷一個對象是否可以轉換為指定的類型,參看實例: TestInstanceof.java 二、下列語句哪一個將引起編譯錯誤?為什麼?哪一個會引起運行時錯誤?為什麼? m=d; d=m; d=(Dog)m; d=c; c=(Cat)m; ...
  • 都說搞C的牛叉,那是因為C解決問題,全靠程式員自己。而我們Java有異常機制,數組越界了會提示,開發效率也比C高,不需要程式員對底層瞭解太多,這一切的一切都歸功於Java Virtual Machine-Java虛擬機。JVM其實就是一個抽象的電腦,它有自己的指令集,有自己的機器語言(ByteCo... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...