ADO.Net中,支持帶參數的SQL語句,例如:Select * from Tables where column1=@column1,其中@column1為SQL參數,使用起來非常方便,而JDBC中沒有找到此功能,感覺有點不便, 於是想自己實現一個.今天正好看見csdn中有一篇http://blog.csdn.net/wallimn/article/details/3734242 文章,有些感觸,於是把自己的實現也寫出來.
我的思路:
1: 在SQL語句中找到以@開始,以" ", "\t", "\n", "\r", ",", ")", ">", "<", "!", "'", "-", "+", "/"為結束的符號,則會認為是SQL參數.
2: 將SQL語句,按@拆分到一個List中,如果是SQL參數,則在使用的時候,替換為相應的參數值.
分析:
1: 該實現模擬了一個ADO.NET的SQL參數功能(SQLClient下)
2: 壞處是如果SQL語句中原來就包含@的非參數字符串,則會被誤認為SQL參數.
實現:
1: 定義SQL語句拆分后的對象,應該包含字符串,以及是否是SQL參數等信息,類如下:
package hij.cache.extension; final class SQLStr { /** * 是否是SQL參數 */ private boolean Param; /** * 對應的文本 */ private String text; /** * 對應的值,一般為Text去除@ */ private String value; public String getValue() { return value; } public boolean isParam() { return Param; } public String getText() { return text; } public void setText(String text) { this.text = text; if(text== null) { return; } if (text.indexOf("@") >= 0) { Param = true; } else { Param = false; } this.text = this.text.replace("\r\n", " ").replace("\r", " ").replace("\t", " ").replace("\n", " "); if (Param) { value = this.text.substring(1); } } }
2: 解析SQL語句,按照@拆分SQL語句,並存儲到List<SQLStr>中.
package hij.cache.extension; import java.util.ArrayList; import java.util.List; import hij.util.generic.IFuncP1; /** * 解析帶參數的SQL語句 * @author XuminRong * */ final class ParseSQL { /** * 根據@解析字符串,並存儲到List中 * @param sql * @return */ public static List<SQLStr> parase(String sql) { List<SQLStr> lst = new ArrayList<SQLStr>(); if (sql == null) { return lst; } int begin = 0; int end = sql.indexOf('@'); while (end >= 0) { String text = sql.substring(begin, end); SQLStr param1 = new SQLStr(); param1.setText(text); lst.add(param1); begin = end; end = getParamEnd(sql, end); if (end != -1) { text = sql.substring(begin, end); SQLStr param2 = new SQLStr(); param2.setText(text); lst.add(param2); } else { break; } begin = end; end = sql.indexOf('@', begin); } if (begin < sql.length()) { String text = sql.substring(begin, sql.length()); SQLStr param = new SQLStr(); param.setText(text); lst.add(param); } return lst; } /** * SQL語句中,SQL參數的結束符 */ static String[] arr = {" ", "\t", "\n", "\r", ",", ")", ">", "<", "!", "'", "-", "+", "/"}; /** * 查找下一個SQL參數的位置 * @param sql * @param begin * @return */ private static int getParamEnd(String sql, int begin) { int index = -1; for (int i = 0; i < arr.length; i++) { int pos = sql.indexOf(arr[i], begin); if (index == -1 && pos != -1) { index = pos; continue; } if (pos != -1 && pos < index) { index = pos; } } return index; } /** * 根據回調函數創建對象 * @param lst * @param callback * @return */ public static String createSQL(List<SQLStr> lst, IFuncP1<String, String> callback) { if (lst == null) { return ""; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < lst.size(); i++) { SQLStr info = lst.get(i); if (!info.isParam()) { sb.append(info.getText()); continue; } if (callback == null) { return ""; } String ret = callback.handle(info.getValue()); sb.append(ret == null? "": ret); } return sb.toString(); } }
測試代碼:
下面是測試代碼:
package hij.cache.extension; import java.util.List; import org.junit.Assert; import org.junit.Test; import hij.util.generic.IFuncP1; public class TestCacheProxy { @Test public void test_Parse_SQL() { String sql = "Select @a @b,@c>,@d<,@e!,@f),'@g',@h\r\n,@i-,@j+,@k/, @l"; List<SQLStr> lst = ParseSQL.parase(sql); String target = ""; for (int i = 0; i < lst.size(); i++) { target += lst.get(i).getText(); } Assert.assertEquals(sql.replace("\r\n", " ").replace("\r", " ").replace("\t", " "), target); sql = "Select @a @b,@c>,@d<,@e!,@f),'@g',@h\r\n,@i-,@j+,@k/"; lst = ParseSQL.parase(sql); target = ""; for (int i = 0; i < lst.size(); i++) { target += lst.get(i).getText(); } Assert.assertEquals(sql.replace("\r\n", " ").replace("\r", " ").replace("\t", " "), target); String sql2 = ParseSQL.createSQL(lst, new IFuncP1<String, String>(){ @Override public String handle(String v) { switch (v) { case "a": { return "a"; } case "b": { return "b"; } case "c": { return "c"; } case "d": { return "d"; } case "e": { return "e"; } case "f": { return "f"; } case "g": { return "g"; } case "h": { return "h"; } case "i": { return "i"; } case "j": { return null; } case "k": { return "k"; } default: { return null; } } } }); Assert.assertEquals(sql2, "Select a b,c>,d<,e!,f),'g',h ,i-,+,k/"); } @Test public void test_Parse_SQL_2() { String sql = "Selecta, b, c, d"; List<SQLStr> lst = ParseSQL.parase(sql); Assert.assertEquals(lst.size(), 1); } }
備注:
1: IFuncP1:
這是一個接口,是我仿照.NET的委托IFunc定義的一個接口,主要是提供一個有返回值且有一個參數的接口,代碼如下:
package hij.util.generic; /** * 單參有返回值接口 * @author XuminRong * * @param <P> * @param <T> * */ public interface IFuncP1<P, T> { public T handle(P p); }
備注2:
1: 看了http://blog.csdn.net/wallimn/article/details/3734242后,發現這個博客的思路比我的好,以后可以參考修改,使用PreparedStatement的內在機制,效率和復雜度應該比自己實現要好.
2: 我當前的實現有問題,我希望能實現:
1) 使用SQL參數
2) 同時可以使用String的format功能,這一點似乎不容易做到.
看了http://blog.csdn.net/wallimn/article/details/3734242后,對其進行重構及測試,下面是相關代碼:
1: 抽象出一個SQL對象:SQLParams,包含SQL語句和參數Map
package hij.cache.extension; import java.util.HashMap; import java.util.Map; public final class SQLParams { String sql; public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } public Map<Integer, String> getParams() { return params; } public void setParams(Map<Integer, String> params) { this.params = params; } Map<Integer, String> params = new HashMap<Integer, String>(); }
2: 添加SQL參數輔助類:這是對NamedParamSqlUtil的重構.(以一個有單參返回值的接口代替fillParameters的pMap,以@代替:)
package hij.cache.extension; import java.sql.PreparedStatement; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import hij.util.generic.IFuncP1; /** * SQL參數處理輔助類 * 參考自:http://blog.csdn.net/wallimn/article/details/3734242 * @author XuminRong * */ public final class SQLParamsUtil { /** * 分析處理帶命名參數的SQL語句。使用Map存儲參數,然后將參數替換成? * @param sql * @return */ public static SQLParams parse(String sql) { SQLParams param = new SQLParams(); String regex = "(@(\\w+))"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(sql); int idx=1; while (m.find()) { //參數名稱可能有重復,使用序號來做Key param.getParams().put(new Integer(idx++), m.group(2)); //System.out.println(m.group(2)); } String result = sql.replaceAll(regex, "?"); param.setSql(result); return param; } /** * 使用參數值Map,填充pStat * @param pStat * @param pMap 命名參數的值表,其中的值可以比較所需的參數多。 * @return */ public static boolean fillParameters(PreparedStatement pStat, SQLParams param, IFuncP1<String,Object> func){ if (pStat == null || param == null) { return false; } if (param.getParams().size() > 0 && func == null) { return false; } for (Integer key : param.getParams().keySet()) { String paramName = param.getParams().get(key); Object val = func.handle(paramName); try { pStat.setObject(key, val); } catch(Exception ex) { ex.printStackTrace(); return false; } } return true; } }
3: 測試程序
@Test public void test_SQLParams_parse() { String sql = "Select @a @b,@c>,@d<,@e!,@f),'@g',@h\r\n,@i-,@j+,@k/, @l"; SQLParams params = SQLParamsUtil.parse(sql); Assert.assertEquals("Select ? ?,?>,?<,?!,?),'?',?\r\n,?-,?+,?/, ?", params.getSql());
Assert.assertEquals(params.getParams().get(3), "c");
}