1. 程式人生 > >Mybatis動態SQL生成

Mybatis動態SQL生成

當前環境下,mybatis是使用很頻繁的一個數據持久層框架。我們很多時候使用xml的方式來配置mybatis的sql,這種方式也稱之為mybatis的動態SQL。but,本篇要說的是另一種方式。日常業務中我們可能會遇到很多有關於動態SQL的問題。我們就需要在程式碼中來編寫SQL。

這個時候有的人可能就會想到直接 String sql = "select * from XXX where XXX";對於這種方式我是不推薦的,1、既然是程式碼,就要有程式碼的易讀性。2、既然是動態sql就要有足夠的擴充套件性。。。。

進入正題:mybatis針對上述問題,提供了一套API來解決我們這個尷尬的窘態。

3.2版本之前:

  SqlBuilder 和 SelectBuilder

 用法:

public String selectBlogsSql() {
  BEGIN(); // Clears ThreadLocal variable
  SELECT("*");
  FROM("BLOG");
  return SQL();
}
private String selectPersonSql() {
  BEGIN(); // Clears ThreadLocal variable
  SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
  SELECT(
"P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON"); FROM("PERSON P"); FROM("ACCOUNT A"); INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID"); INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID"); WHERE("P.ID = A.ID"); WHERE("P.FIRST_NAME like ?"); OR(); WHERE("P.LAST_NAME like ?"); GROUP_BY("P.ID"); HAVING(
"P.LAST_NAME like ?"); OR(); HAVING("P.FIRST_NAME like ?"); ORDER_BY("P.ID"); ORDER_BY("P.FULL_NAME"); return SQL(); }

具體相關文件:http://www.mybatis.org/mybatis-3/zh/statement-builders.html

在3.2版本之前,通過實現ThreadLocal變數來掩蓋一些導致Java DSL麻煩的語言限制。但這種方式已經廢棄了,現代的框架都歡迎人們使用構建器型別和匿名內部類的想法。因此,SelectBuilder 和 SqlBuilder 類都被廢棄了。

 

3.2版本之後:

  例項:

private String selectPersonSql() {
  return new SQL() {{
    SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
    SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
    FROM("PERSON P");
    FROM("ACCOUNT A");
    INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
    INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
    WHERE("P.ID = A.ID");
    WHERE("P.FIRST_NAME like ?");
    OR();
    WHERE("P.LAST_NAME like ?");
    GROUP_BY("P.ID");
    HAVING("P.LAST_NAME like ?");
    OR();
    HAVING("P.FIRST_NAME like ?");
    ORDER_BY("P.ID");
    ORDER_BY("P.FULL_NAME");
  }}.toString();
}

 

————————————————————————————————————————————————————————————————————————————————————————

當然上述都是這些Mybatis本身提供的一些API,要達到動態SQL,我們還差一步,所以來個簡單的demo。

public class MybatisSQLTemplate {

    /**
     * 插入資料
     * @param params
     * @return
     */
    public String insert(Map<String,Object> params){
        return new SQL()
                .INSERT_INTO(getTableName(params))
                .VALUES(buildFiled(params),buildValue(params))
                .toString();
    }

    /**
     * 更新資料
     * @param params
     * @return
     */
    public String update(Map<String,Object> params){
        return new SQL()
                .UPDATE(getTableName(params))
                .SET(buildSet(params))
                .WHERE(buildWhere(params))
                .toString();
    }

    /**
     * 獲取表名
     * @param params
     * @return
     */
    private String getTableName(Map<String,Object> params){
        if(params == null || params.get(Condition.TABLE_NAME.name()) == null){
            throw new IllegalArgumentException("Table name is required..");
        }else {
            return params.get(Condition.TABLE_NAME.name()).toString();
        }
    }

    /**
     * 構建插入欄位
     * @param params
     * @return
     */
    private String buildFiled(Map<String,Object> params){
        params.remove(Condition.TABLE_NAME.name());
        if(params.isEmpty()){
            throw new IllegalArgumentException("Parameter name is required..");
        }
        String columns = params.keySet().toString();
        return columns.substring(1,columns.length()-1);
    }

    /**
     * 構建插入value值
     * @param params
     * @return
     */
    private String buildValue(Map<String,Object> params){
        params.remove(Condition.TABLE_NAME.name());
        if(params.isEmpty()){
            throw new IllegalArgumentException("Parameter value is required..");
        }
        String columns = params.keySet().stream().map(i->"#{"+i+"}").collect(Collectors.toList()).toString();
        return columns.substring(1,columns.length()-1);
    }

    /**
     * 構建更新set資料
     * @param params
     * @return
     */
    private String buildSet(Map<String,Object> params){
        params.remove(Condition.TABLE_NAME.name());
        if(params.isEmpty()){
            throw new IllegalArgumentException("The value to be modified cannot be empty..");
        }
        Object temp = params.remove(Condition.WHERE.name());
        String sets = params.keySet().stream().map(i -> i + "=#{" + i + "}").collect(Collectors.toList()).toString();
        params.put(Condition.WHERE.name(),temp);
        return sets.substring(1,sets.length()-1);
    }


    /**
     * 構建where條件(目前只支援單個條件)
     * @param params
     * @return
     */
    private String buildWhere(Map<String,Object> params){
        params.remove(Condition.TABLE_NAME.name());
        if(params.isEmpty() || params.get(Condition.WHERE.name()) == null){
            throw new IllegalArgumentException("Missing where condition..");
        }
        Object where = params.get(Condition.WHERE.name());
        //可以做多型別判斷以相容更復雜where條件
        if(where instanceof String){
            String temp = (String) where;
            return temp + "= #{" + where +"}";
        }else {
            throw new IllegalArgumentException("Where argument is illegal..");
        }
    }
}

說明一下,上述的程式碼中使用的引數都是Map。一是為了測試方便,而是降低複雜度。當然你也可以傳入自己的db物件。通過反射機制來讀取屬性,或者通過自定義註解來使整個程式碼結構更合理。

 

接下來說明一下,如何使用。

首先了解一下註解@SelectProvider、@InsertProvider、@UpdateProvide、@DeleteProvider,這四個註解分別用於查、插、改、刪,都擁有一個type屬性以及一個method屬性。type屬性用來指明生成SQL的類是那個,method用來表明該類下的那個方法。

public interface OrderInfoMapper {
@InsertProvider(type = MybatisSQLTemplate.class,method = "insert")
int insert(Map<String,Object> params);

@UpdateProvider(type = MybatisSQLTemplate.class,method = "update")
int update(Map<String,Object> params);
}

到這裡動態SQL就算完成了。這裡的Mapper與XML方式對應的Mapper無異。

寫在後面:當然MyBatis不僅提供了API方式的SQL,結果集引數等的對映都是可以做到的。而且如果想要自定義Mybatis的一些行為,可以通過編寫外掛的方式來實現 @Intercepts。

 

2018-11-08 記