組合( Composite )模式就是把對象組合成樹形結構,以表示「部分-總體」的層次結構,用戶能夠像處理一個簡單對象同樣來處理一個複雜對象,從而使得調用者無需瞭解複雜元素的內部結構。前端
組合模式中的角色有:java
具體組合模式的例子能夠參考 設計模式整理 node
如今咱們來講一下SqlNode是什麼,來看這麼一段配置文件正則表達式
<select id="findByGameTypeCount" resultType="java.lang.Long"> select count(*) from betdetails a inner join UserBetOrder b on a.orderId = b.id <where> <if test="gameType != null and gameType > 0"> a.gameType = #{gameType} and </if> <if test="currDrawno != null"> b.currentDrawno = #{currDrawno} and </if> <if test="orderId != null and orderId > 0"> a.orderId = #{orderId} and </if> <if test="status != null and status >= 0"> a.status = #{status} and </if> <if test="userId != null and userId > 0"> b.userId = #{userId} and </if> <if test="start != null"> a.createTime >= #{start} and </if> <if test="end != null"> a.createTime <= #{end} and </if> 1 = 1 </where> </select>
<insert id="insertBetdetailsByBatch" parameterType="java.util.List"> insert into betdetails(id,orderId,actorIndex,createTime,ballIndex,ballValue,betAmount,rate1,rate2,rate3,gameType,status,betResult,awardAmount,ballName) values <foreach collection="list" item="item" index="index" separator=","> (#{item.id},#{item.orderId},#{item.actorIndex},#{item.createTime},#{item.ballIndex},#{item.ballValue},#{item.betAmount},#{item.rate1},#{item.rate2},#{item.rate3},#{item.gameType},#{item.status},#{item.betResult},#{item.awardAmount},#{item.ballName}) </foreach> </insert>
這其中的<if><where><foreach>節點就是SqlNode節點,SqlNode是一個接口,表明着組合模式中的容器。只要是有SqlNode,那就表明着必定是一個動態的SQL,裏面就有可能會有參數#{}sql
public interface SqlNode { //SqlNode接口中定義的惟一方法,該方法會根據用戶傳入的實參,解析該SqlNode所記錄的動態SQL節點,並調用DynamicContext.appendSql()方法將解析後的SQL片斷追加到 //DynamicContext.sqlBuilder中保存 //當SQL節點下的全部SqlNode完成解析後,就能夠從DynamicContext中獲取一條動態生成的完整的SQL語句 boolean apply(DynamicContext context); }
咱們先來看一下DynamicContext是什麼,它的核心字段以下express
private final ContextMap bindings; //參考上下文 //在SqlNode解析動態SQL時,會將解析後的SQL語句片斷添加到該屬性中保存,最終拼湊出一條完成的SQL語句 private final StringBuilder sqlBuilder = new StringBuilder();
ContextMap是一個內部類,繼承於HashMap,重寫了get方法後端
static class ContextMap extends HashMap<String, Object> { private static final long serialVersionUID = 2977601501966151582L; //將用戶傳入的參數封裝成MetaObject對象(類實例中檢查類的屬性是否包含getter,setter方法) private MetaObject parameterMetaObject; public ContextMap(MetaObject parameterMetaObject) { this.parameterMetaObject = parameterMetaObject; } @Override public Object get(Object key) { String strKey = (String) key; //若是ContextMap中已經包含了該key,則直接返回 if (super.containsKey(strKey)) { return super.get(strKey); } //若是不包含該key,從parameterMetaObject中查找對應屬性 if (parameterMetaObject != null) { // issue #61 do not modify the context when reading return parameterMetaObject.getValue(strKey); } return null; } }
public void appendSql(String sql) { sqlBuilder.append(sql); sqlBuilder.append(" "); }
public void bind(String name, Object value) { bindings.put(name, value); }
SqlNode的實現類以下設計模式
其中MixedSqlNode是樹枝,TextSqlNode是樹葉....數組
咱們先來看一下TextSqlNode,TextSqlNode表示的是包含${}佔位符的動態SQL節點。它的接口實現方法以下緩存
@Override public boolean apply(DynamicContext context) { //將動態SQL(帶${}佔位符的SQL)解析成完成SQL語句的解析器,即將${}佔位符替換成實際的變量值 GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter)); //將解析後的SQL片斷添加到DynamicContext中 context.appendSql(parser.parse(text)); return true; }
BindingTokenParser是TextNode中定義的內部類,繼承了TokenHandler接口,它的主要做用是根據DynamicContext.bindings集合中的信息解析SQL語句節點中的${}佔位符。
private DynamicContext context;
private Pattern injectionFilter; //須要匹配的正則表達式
@Override public String handleToken(String content) { //獲取用戶提供的實參 Object parameter = context.getBindings().get("_parameter"); //若是實參爲null if (parameter == null) { //將參考上下文的value key設爲null context.getBindings().put("value", null); //若是實參是一個經常使用數據類型的類(Integer.class,String.class,Byte.class等等) } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) { //將參考上下文的value key設爲該實參 context.getBindings().put("value", parameter); } //經過OGNL解析參考上下文的值 Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null" //檢測合法性 checkInjection(srtValue); return srtValue; }
private void checkInjection(String value) { if (injectionFilter != null && !injectionFilter.matcher(value).matches()) { throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern()); } }
在OgnlCache中,對原生的OGNL進行了封裝。OGNL表達式的解析過程是比較耗時的,爲了提升效率,OgnlCache中使用了expressionCashe字段(ConcurrentHashMap<String,Object>類型)對解析後的OGNL表達式進行緩存。爲了說明OGNL,咱們先來看一個例子
@Data @ToString public class User { private int id; private String name; }
public class OGNLDemo { public void testOgnl1() throws OgnlException { OgnlContext context = new OgnlContext(); context.put("cn","China"); String value = (String) context.get("cn"); System.out.println(value); User user = new User(); user.setId(100); user.setName("Jack"); context.put("user",user); Object u = context.get("user"); System.out.println(u); Object ognl = Ognl.parseExpression("#user.id"); Object value1 = Ognl.getValue(ognl,context,context.getRoot()); System.out.println(value1); User user1 = new User(); user1.setId(200); user1.setName("Mark"); context.setRoot(user1); Object ognl1 = Ognl.parseExpression("id"); Object value2 = Ognl.getValue(ognl1,context,context.getRoot()); System.out.println(value2); Object ognl2 = Ognl.parseExpression("@@floor(10.9)"); Object value3 = Ognl.getValue(ognl2, context, context.getRoot()); System.out.println(value3); } public static void main(String[] args) throws OgnlException { OGNLDemo demo = new OGNLDemo(); demo.testOgnl1(); } }
運行結果:
China
User(id=100, name=Jack)
100
200
10.0
private static final Map<String, Object> expressionCache = new ConcurrentHashMap<String, Object>();
public static Object getValue(String expression, Object root) { try { //建立OgnlContext對象 Map<Object, OgnlClassResolver> context = Ognl.createDefaultContext(root, new OgnlClassResolver()); //使用OGNL執行expression表達式 return Ognl.getValue(parseExpression(expression), context, root); } catch (OgnlException e) { throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e); } }
private static Object parseExpression(String expression) throws OgnlException { //查找緩存 Object node = expressionCache.get(expression); if (node == null) { //解析表達式 node = Ognl.parseExpression(expression); //將表達式的解析結果添加到緩存中 expressionCache.put(expression, node); } return node; }
StaticTextSqlNode很簡單,就是直接返回SQL語句
public class StaticTextSqlNode implements SqlNode { private final String text; public StaticTextSqlNode(String text) { this.text = text; } @Override public boolean apply(DynamicContext context) { context.appendSql(text); return true; } }
IfSqlNode是解析<if>節點,字段含義以下
//用於解析<if>節點的test表達式的值 private final ExpressionEvaluator evaluator; //記錄<if>節點中test表達式 private final String test; //記錄了<if>節點的子節點 private final SqlNode contents;
接口方法以下
@Override public boolean apply(DynamicContext context) { //檢測test屬性中記錄的表達式 if (evaluator.evaluateBoolean(test, context.getBindings())) { //若是test表達式爲true,則執行子節點的apply()方法 contents.apply(context); return true; //返回test表達式的結果爲true } return false; //返回test表達式的結果爲false }
在ExpressionEvaluator中
public boolean evaluateBoolean(String expression, Object parameterObject) { //用OGNL解析expression表達式 Object value = OgnlCache.getValue(expression, parameterObject); //處理Boolean類型 if (value instanceof Boolean) { return (Boolean) value; } //處理數字類型 if (value instanceof Number) { return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0; } return value != null; }
TrimSqlNode會根據子節點的解析結果,添加或刪除響應的前綴或後綴,好比有這麼一段配置
<insert id="insertNotNullBetdetails" parameterType="com.cloud.model.game.Betdetails"> insert into betdetails <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null">id,</if> <if test="orderId != null">orderId,</if> <if test="actorIndex != null">actorIndex,</if> <if test="ballIndex != null">ballIndex,</if> <if test="ballValue != null">ballValue,</if> <if test="betAmount != null">betAmount,</if> <if test="createTime != null">createTime,</if> <if test="rate1 != null">rate1,</if> <if test="rate2 != null">rate2,</if> <if test="rate3 != null">rate3,</if> <if test="gameType != null">gameType,</if> <if test="status != null">status,</if> <if test="betResult != null">betResult,</if> <if test="awardAmount != null">awardAmount,</if> <if test="ballName != null">ballName,</if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null">#{id},</if> <if test="orderId != null">#{orderId},</if> <if test="actorIndex != null">#{actorIndex},</if> <if test="createTime != null">#{createTime},</if> <if test="ballIndex != null">#{ballIndex},</if> <if test="ballValue != null">#{ballValue},</if> <if test="betAmount != null">#{betAmount},</if> <if test="rate1 != null">#{rate1},</if> <if test="rate2 != null">#{rate2},</if> <if test="rate3 != null">#{rate3},</if> <if test="gameType != null">#{gameType},</if> <if test="status != null">#{status},</if> <if test="betResult != null">#{betResult},</if> <if test="awardAmount != null">#{awardAmount},</if> <if test="ballName != null">#{ballName},</if> </trim> </insert>
TrimSqlNode中字段含義以下
private final SqlNode contents; //該<trim>節點的子節點 private final String prefix; //記錄了前綴字符串(爲<trim>節點包裹的SQL語句添加的前綴) private final String suffix; //記錄了後綴字符串(爲<trim>節點包裹的SQL語句添加的後綴) //若是<trim>節點包裹的SQL語句是空語句,刪除指定的前綴,如where private final List<String> prefixesToOverride; //若是<trim>節點包裹的SQL語句是空語句,刪除指定的後綴,如逗號 private final List<String> suffixesToOverride;
它的接口方法以下
@Override public boolean apply(DynamicContext context) { //建立FilteredDynamicContext對象,FilteredDynamicContext是TrimSqlNode的內部類,繼承於DynamicContext FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context); //調用子節點的apply()方法進行解析,注意收集SQL語句的是filteredDynamicContext boolean result = contents.apply(filteredDynamicContext); //處理前綴和後綴 filteredDynamicContext.applyAll(); return result; }
FilteredDynamicContext爲DynamicContext的代理類,它的字段屬性含義以下
private DynamicContext delegate; //底層封裝委託的DynamicContext對象 private boolean prefixApplied; //是否已經處理過前綴 private boolean suffixApplied; //是否已經處理事後綴 private StringBuilder sqlBuffer; //用於記錄子節點解析後的結果
FilteredDynamicContext的applyAll()方法
public void applyAll() { //獲取子節點解析後的結果,並所有轉化爲大寫 sqlBuffer = new StringBuilder(sqlBuffer.toString().trim()); String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH); if (trimmedUppercaseSql.length() > 0) { //處理前綴 applyPrefix(sqlBuffer, trimmedUppercaseSql); //處理後綴 applySuffix(sqlBuffer, trimmedUppercaseSql); } //將解析後的結果SQL片斷添加到DynamicContext的StringBuilder中 delegate.appendSql(sqlBuffer.toString()); }
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) { if (!prefixApplied) { //若是尚未處理過前綴 prefixApplied = true; //更新爲已處理 if (prefixesToOverride != null) { //若是須要刪除的前綴列表不爲null //遍歷該前綴列表 for (String toRemove : prefixesToOverride) { //若是<trim>子節點收集上來的SQL語句以該前綴開頭 if (trimmedUppercaseSql.startsWith(toRemove)) { //從<trim>子節點收集上來的StringBuilder中刪除該前端 sql.delete(0, toRemove.trim().length()); break; } } } //若是有前綴字符串(好比說"("),將前綴字符串插入StringBuilder最前端 if (prefix != null) { sql.insert(0, " "); sql.insert(0, prefix); } } }
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) { if (!suffixApplied) { //若是尚未處理事後綴 suffixApplied = true; //更新爲已處理後綴 if (suffixesToOverride != null) { //若是須要處理的後綴列表不爲null //遍歷該後綴列表 for (String toRemove : suffixesToOverride) { //若是從<trim>子節點收集上來的SQL語句以該後綴結尾 if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) { //獲取該後綴的起始位置 int start = sql.length() - toRemove.trim().length(); //獲取該後綴的終止位置 int end = sql.length(); //從<trim>子節點收集上來的StringBuilder中刪除該後端 sql.delete(start, end); break; } } } //若是有後綴字符串(好比說")"),將前綴字符串拼接上StringBuilder最後端 if (suffix != null) { sql.append(" "); sql.append(suffix); } } }
WhereSqlNode和SetSqlNode都繼承於TrimSqlNode,他們只是在TrimSqlNode的屬性中指定了固定的標記。
public class WhereSqlNode extends TrimSqlNode { private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t"); public WhereSqlNode(Configuration configuration, SqlNode contents) { super(configuration, contents, "WHERE", prefixList, null, null); } }
public class SetSqlNode extends TrimSqlNode { private static List<String> suffixList = Arrays.asList(","); public SetSqlNode(Configuration configuration,SqlNode contents) { super(configuration, contents, "SET", null, null, suffixList); } }
ForEachSqlNode,在動態SQL語句中,一般須要對一個集合進行迭代,Mybatis提供了<foreach>標籤實現該功能。在使用<foreach>標籤迭代集合時,不只可使用集合的元素和索引值,還能夠在循環開始以前或結束以後添加指定的字符串,也容許在迭代過程當中添加指定的分隔符。配置樣例以下
List參數的
<insert id="insertBetdetailsByBatch" parameterType="java.util.List"> insert into betdetails(id,orderId,actorIndex,createTime,ballIndex,ballValue,betAmount,rate1,rate2,rate3,gameType,status,betResult,awardAmount,ballName) values <foreach collection="list" item="item" index="index" separator=","> (#{item.id},#{item.orderId},#{item.actorIndex},#{item.createTime},#{item.ballIndex},#{item.ballValue},#{item.betAmount},#{item.rate1},#{item.rate2},#{item.rate3},#{item.gameType},#{item.status},#{item.betResult},#{item.awardAmount},#{item.ballName}) </foreach> </insert>
Map參數的
<select id="findAgentUserBalance" parameterType="java.util.Map" resultType="java.math.BigDecimal"> SELECT SUM(banlance) FROM app_user <where> <if test="null != userIds"> AND id IN <foreach collection="userIds" item="item" index="index" separator="," open="(" close=")"> #{item} </foreach> </if> </where> </select>
ForEachSqlNode中各個字段含義以下:
public static final String ITEM_PREFIX = "__frch_";
//用於判斷循環的終止條件 private final ExpressionEvaluator evaluator; //迭代的集合表達式 private final String collectionExpression; //記錄了該ForEachSqlNode節點的子節點 private final SqlNode contents; //在循環開始前要添加的字符串 private final String open; //在循環結束後要添加的字符串 private final String close; //循環過程當中,每項之間的分隔符 private final String separator; //本次迭代的集合元素標識(至關於一個變量,用該變量來識別) private final String item; //本次迭代的集合索引標識(至關於一個變量,用該變量來識別) private final String index; //全局配置信息 private final Configuration configuration;
接口方法爲
@Override public boolean apply(DynamicContext context) { //獲取用戶傳入的參數的上下文 Map<String, Object> bindings = context.getBindings(); //將參數上下文做爲root傳入OGNL解析出相似#{item.id}的原值後將迭代的集合表達式還原成集合自己 final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings); //若是集合沒有數據,直接返回true if (!iterable.iterator().hasNext()) { return true; } boolean first = true; //在循環開始前處理要添加的字符SQL片斷 applyOpen(context); int i = 0; //開始遍歷集合,進入循環 for (Object o : iterable) { //將context緩存爲另外一個對象 DynamicContext oldContext = context; //若是是集合的第一項,或者分隔符爲null if (first || separator == null) { //以空前綴來構建context爲PrefixedContext對象 context = new PrefixedContext(context, ""); } else { //若是不是集合第一項,或者分隔符不爲null,以分隔符爲前綴來構建context爲PrefixedContext對象 context = new PrefixedContext(context, separator); } //獲取迭代計數器 int uniqueNumber = context.getUniqueNumber(); // Issue #709 if (o instanceof Map.Entry) { //若是集合是Map類型,將集合中key和value添加到DynamicContext.bindings集合中保存 @SuppressWarnings("unchecked") Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o; applyIndex(context, mapEntry.getKey(), uniqueNumber); applyItem(context, mapEntry.getValue(), uniqueNumber); } else { //將集合中的索引和元素添加到DynamicContext.bindings集合中保存 applyIndex(context, i, uniqueNumber); applyItem(context, o, uniqueNumber); } //調用子節點的apply()收集SQL語句,放入DynamicContext的代理類FilteredDynamicContext的StringBuilder中 //此處解析的是類如#{_frch_index_0},#{__frch_item_0}的標識 contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber)); if (first) { //若是是第一項,將first更新爲false(在applyOpen(context)中,prefixApplied已經被更新爲true) first = !((PrefixedContext) context).isPrefixApplied(); } //還原context爲原始DynamicContext,從新進入下一項循環 context = oldContext; i++; } //在循環結束後添加要處理的SQL片斷 applyClose(context); context.getBindings().remove(item); context.getBindings().remove(index); return true; }
private void applyOpen(DynamicContext context) { if (open != null) { //將開始循環前的SQL片斷添加到DynamicContext的StringBuilder中 context.appendSql(open); } }
private void applyIndex(DynamicContext context, Object o, int i) { if (index != null) { //將集合的索引標識與放入的對象(map對象放入的是key,List對象放入的是真正的索引值)存入參考上下文中 //以上面配置的Map爲例,假如傳入的userIds爲("小李飛刀",1),("霸天虎",2),此處假設用戶名不能重複,且執行到第一個 //此處存入的是("index","小李飛刀"),若是執行到第二個的時候,此處存入的是("index","霸天虎") context.bind(index, o); //將集合的索引標識與計數器的鏈接綁定,與放入的對象存入參考上下文中 //此處存入的是("_frch_index_0","小李飛刀"),執行到第二個的時候,此處存入的是("_frch_index_1","霸天虎") context.bind(itemizeItem(index, i), o); } }
private static String itemizeItem(String item, int i) { //返回的值相似爲__frch_item_0(假設item="item",i=0) return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString(); }
private void applyItem(DynamicContext context, Object o, int i) { if (item != null) { //將集合的內容標識與放入的對象(map對象放入的爲value,List對象放入的爲列表中的對象元素)存入參考上下文中 //對應索引的內容,此處存入的是("item",1),執行到第二個的時候,此處存入的是("item",2) context.bind(item, o); //將集合的內容標識與計數器的鏈接綁定,與放入的對象存入參考上下文中,爲子節點的進一步解析(真正替換每一次迭代項爲集合實際的值)作準備 //此處存入的是("__frch_item_0",1),執行到第二個的時候,此處存入的是("__frch_item_1",2) context.bind(itemizeItem(item, i), o); } }
private void applyClose(DynamicContext context) { if (close != null) { //將結束循環後的SQL片斷添加到DynamicContext的StringBuilder中 context.appendSql(close); } }
PrefixedContext繼承於DynamicContext,其實就是DynamicContext的一個代理類,各字段的含義以下
private final DynamicContext delegate; //對DynamicContext的委託 private final String prefix; //指定的前綴 private boolean prefixApplied; //是否已經處理過前綴
最主要的地方就是它重寫了appendSql()方法,其餘地方都是經過對delegate的委託,來實現同樣的功能
@Override public void appendSql(String sql) { //若是沒有處理過前綴且要添加的SQL語句不爲空 if (!prefixApplied && sql != null && sql.trim().length() > 0) { //先把指定的前綴SQL片斷添加到delegate中 delegate.appendSql(prefix); //修改成前綴已處理 prefixApplied = true; } //再把處理完的SQL語句片斷添加到StringBuilder中 delegate.appendSql(sql); }
FilteredDynamicContext爲DynamicContext的代理類,負責處理#{}佔位符,它的各個字段含義以下
private final DynamicContext delegate; //委託的DynamicContext private final int index; //對應集合項在集合中的索引位置 private final String itemIndex; //對應集合項的index(索引標識) private final String item; //對應集合項的item(元素標識)
重寫了appendSql()方法
@Override public void appendSql(String sql) { //解析類如("_frch_index_0","小李飛刀"),("__frch_item_0",1)的映射,給_frch_index_0,__frch_item_0變爲OGNL能夠識別的 //#{_frch_index_0},#{__frch_item_0} GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() { @Override public String handleToken(String content) { String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index)); if (itemIndex != null && newContent.equals(content)) { newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index)); } return new StringBuilder("#{").append(newContent).append("}").toString(); } }); //經過OGNL來進行解析成對應映射的值 delegate.appendSql(parser.parse(sql)); }
在ExpressionEvaluator中
首先咱們要知道,全部的集合接口(好比List,Set)都繼承於Iterable<?>
public interface Collection<E> extends Iterable<E>
public Iterable<?> evaluateIterable(String expression, Object parameterObject) { //使用OGNL來解析expression Object value = OgnlCache.getValue(expression, parameterObject); if (value == null) { throw new BuilderException("The expression '" + expression + "' evaluated to a null value."); } //處理可迭代集合類型 if (value instanceof Iterable) { return (Iterable<?>) value; } //處理數組類型,轉化爲List,並返回該List if (value.getClass().isArray()) { // the array may be primitive, so Arrays.asList() may throw // a ClassCastException (issue 209). Do the work manually // Curse primitives! :) (JGB) int size = Array.getLength(value); List<Object> answer = new ArrayList<Object>(); for (int i = 0; i < size; i++) { Object o = Array.get(value, i); answer.add(o); } return answer; } //處理Map類型,返回Map的entrySet集合(Set) if (value instanceof Map) { return ((Map) value).entrySet(); } throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable."); }
ChooseSqlNode,若是在編寫動態SQL語句時須要相似Java中的switch語句的功能,可使用<choose>、<when>、<otherwise>三個標籤的組合。配置樣例以下
<select id="findList" parameterType="java.util.Map" resultType="com.cloud.model.user.AppUser"> SELECT a.id,a.username,a.password,a.nickname,a.headImgUrl,a.phone,a.sex,a.enabled,a.type,a.createTime,a.updateTime,a.banlance,a.control,a.win,a.loginTime FROM app_user a <if test="groupId != null"> <choose> <when test="groupId != 0"> INNER JOIN user_group b ON a.id=b.user_id AND b.group_id=#{groupId} </when> <otherwise> LEFT JOIN user_group b ON a.id=b.user_id </otherwise> </choose> </if> </select>
ChooseSqlNode中各字段的含義以下
private final SqlNode defaultSqlNode; //<otherwise>節點對應的SqlNode private final List<SqlNode> ifSqlNodes; //<when>節點對應的IfSqlNode集合
接口方法以下
@Override public boolean apply(DynamicContext context) { //遍歷IfSqlNodes集合,並調用其中SqlNode對象的apply()方法 for (SqlNode sqlNode : ifSqlNodes) { if (sqlNode.apply(context)) { return true; } } //調用defaultSqlNode.apply()方法 if (defaultSqlNode != null) { defaultSqlNode.apply(context); return true; } return false; }
VarDeclSqlNode表示的是動態SQL語句中的<bind>節點,咱們來看一下<bind>節點的用法
<if test="userNarne != null and userNarne != ' '"> and user name like concat('毛',#{userNarne},'氈') </if>
改成
<if test="userName != null and userName !=' '"> <bind name="userNarneLike" value="'毛' + userName + '氈'"/> and user name like #{userNarneLike} </if>
VarDeclSqlNode的字段內容以下
private final String name; //<bind>節點名稱 private final String expression; //<bind>節點的value表達式
接口方法
@Override public boolean apply(DynamicContext context) { //解析OGNL表達式的值 final Object value = OgnlCache.getValue(expression, context.getBindings()); //將name,value存入DynamicContext的bindings集合中,提供其餘SqlNode的下一步解析 context.bind(name, value); return true; }