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 記