以前mybatis中<where>、<update>、<if>、<foreach>標籤用的多,知道有<trim>這個標籤,但不多去用,也沒有去深刻理解它,直到最近遇到一個問題。問題是這樣的:html
一個SQL有三個int查詢字段a、b、c,表達式爲:a=#{a} AND (b=#{b} OR c=#{c})。其中a是必查的,b和c爲非必查的(這裏假定傳入-1表示該字段不參與查詢)。那麼該表達式會有如下幾種形態:java
看到這個需求後,以爲邏輯仍是挺簡單的,但寫起mapper的SQL來並非那麼容易(若是你有的話,歡迎下邊評論貼出來)。考慮了多層<if>、<choose>等標籤,雖然也能實現這個功能,但過於繁瑣。有沒有一種更簡單的實現方式?有!<trim>!!!sql
請尊重做者勞動成果,轉載請標明原文連接:http://www.cnblogs.com/waterystone/p/7070836.htmlexpress
網上關於<trim>的介紹並很少,經過看mybatis的源碼,一句話描述trim的功能:子句首尾的刪除與添加。它就是一個字符串處理工具,相似於replace(),但它只處理首尾。真的是一個神器,其實mybatis中的<set>和<where>均可以用<trim>來實現,但<trim>的功能更強大,使用起來更靈活!!!apache
<trim prefix="(" prefixOverrides="OR" suffixOverrides="," suffix=")">子句</trim>session
這裏的子句會對其進行trim()處理,忽略掉換行、空格等字符,因此本文中的子句都是指trim()處理後的字符串。若是子句爲空,那麼整個<trim>塊不起做用,至關於不存在。本<trim>塊的做用就是:去掉子句首的OR和子句尾的逗號,並在子句先後分別加上(和),好比,"orabc,"-->"(abc)"。mybatis
有了這個神器,處理前文提起的需求,就能夠用<trim>很悠然的處理了。 app
WHERE a = #{a} <trim prefix="AND(" prefixOverrides="OR" suffix=")"> <if test="b != -1"> OR b = #{b} </if> <if test="c != -1"> OR c = #{c} </if> </trim>
<trim>的實現很是簡單,若是以爲我描述的還不夠清楚,能夠看下第三部分的源碼。less
/** * Copyright 2009-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.scripting.xmltags; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import org.apache.ibatis.session.Configuration; /** * @author Clinton Begin */ public class TrimSqlNode implements SqlNode { private SqlNode contents; private String prefix;//前綴 private String suffix;//後綴 private List<String> prefixesToOverride;//子句首命中詞列表 private List<String> suffixesToOverride;//子句尾命中詞列表 private Configuration configuration; public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) { this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));//解析以|分隔的命中詞 } protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) { this.contents = contents; this.prefix = prefix; this.prefixesToOverride = prefixesToOverride; this.suffix = suffix; this.suffixesToOverride = suffixesToOverride; this.configuration = configuration; } @Override public boolean apply(DynamicContext context) { FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context); boolean result = contents.apply(filteredDynamicContext); filteredDynamicContext.applyAll();//解析<trim> return result; } //將命中詞以|分隔,獲取命中詞列表 private static List<String> parseOverrides(String overrides) { if (overrides != null) { final StringTokenizer parser = new StringTokenizer(overrides, "|", false); final List<String> list = new ArrayList<String>(parser.countTokens()); while (parser.hasMoreTokens()) { list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));//命中詞統一轉爲大寫。 } return list; } return Collections.emptyList(); } private class FilteredDynamicContext extends DynamicContext { private DynamicContext delegate; private boolean prefixApplied;//前綴處理標記,表示是否處理過。 private boolean suffixApplied;//後綴處理標記,表示是否處理過。 private StringBuilder sqlBuffer; public FilteredDynamicContext(DynamicContext delegate) { super(configuration, null); this.delegate = delegate; this.prefixApplied = false; this.suffixApplied = false; this.sqlBuffer = new StringBuilder(); } public void applyAll() { sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());//子句先trim() String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);//跟命中詞一致,子句也統一轉爲大寫,因此<trim>對子句的處理是忽略大小寫的。 if (trimmedUppercaseSql.length() > 0) {//若是子句非空才處理 applyPrefix(sqlBuffer, trimmedUppercaseSql);//處理前綴 applySuffix(sqlBuffer, trimmedUppercaseSql);//處理後綴 } delegate.appendSql(sqlBuffer.toString()); } @Override public Map<String, Object> getBindings() { return delegate.getBindings(); } @Override public void bind(String name, Object value) { delegate.bind(name, value); } @Override public int getUniqueNumber() { return delegate.getUniqueNumber(); } @Override public void appendSql(String sql) { sqlBuffer.append(sql); } @Override public String getSql() { return delegate.getSql(); } private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) { if (!prefixApplied) {//只處理一次 prefixApplied = true; if (prefixesToOverride != null) {//若是命中詞列表非空 for (String toRemove : prefixesToOverride) {//輪詢命中詞列表 if (trimmedUppercaseSql.startsWith(toRemove)) { sql.delete(0, toRemove.trim().length());//若是句首命中,則刪除該句首的命中詞 break;//最多刪除一次,不會把全部命中詞都匹配一遍 } } } if (prefix != null) {//加上前綴。刪除原句首,再添上新句首,至關於句首的替換。 sql.insert(0, " "); sql.insert(0, prefix); } } } private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) { if (!suffixApplied) {//只處理一次 suffixApplied = true; if (suffixesToOverride != null) {//若是命中詞列表非空 for (String toRemove : suffixesToOverride) {//輪詢命中詞列表 if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) { int start = sql.length() - toRemove.trim().length(); int end = sql.length(); sql.delete(start, end);//若是句尾命中,則刪除該句尾的命中詞 break;//最多刪除一次,不會把全部命中詞都匹配一遍 } } } if (suffix != null) {//加上後綴。刪除原句尾,再添上新句尾,至關於句尾的替換。 sql.append(" "); sql.append(suffix); } } } } }