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”);
}