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