我写了一个可以把非参数SQL语句转化为参数SQL语句的代码,希望大家帮我测试一下他的健壮性
本来我希望能用在公司的项目里,但是经理说不可靠;可是我测试了几万次也没有发现问题;
希望大家能帮我测试一下,找到问题并且回复的话我一定会给分的;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/**
 * 把非参数SQL语句转化为参数SQL语句,只有数字(包括小数)和字符串会转化为参数
 * 
 * 用法: 
 * ToPrepareSQL toPrep = new ToPrepareSQL(sql);//sql表示非参数SQL语句
 * toPrep.transform());//得到转化的参数SQL语句
 * toPrep.getParams();//得到参数列表
 * 
 * @date 20060508
 * @author lijiuwei
 */
public class ToPrepareSQL { private char[] sqlarr = null; private List paramList = new ArrayList(); private static int index = 0; // 检测是否是insert values SQL语句,忽略大小写
private static Pattern insValPattern = Pattern.compile(
"^(insert into)(.+?)values(\\s*)\\(", Pattern.CASE_INSENSITIVE); // 检测是否是数字(包括小数)
private static Pattern digitPattern = Pattern
.compile("(\\d+)|(\\d+\\.\\d+)"); // 支持的可以转化为参数SQL语句的SQL类型
private static final String[] supEntryWordArr = { "SELECT", "UPDATE",
"DELETE", "INSERT" };

//测试使用
private static long transfromCount = 0;//把非参数SQL语句转化为参数SQL语句的总次数

private static long costMillisCount =0;//花费的总时间(毫秒为单位)

/**
 * 得到把非参数SQL语句转化为参数SQL语句的总次数
 * @return
 */
public static long getTransfromCount(){
return transfromCount;
}

/**
 * 得到花费的总时间(毫秒为单位)
 * @return
 */
public static long getCostMillisCount(){
return costMillisCount;
}

/**
 * 得到平均处理每一个SQL的花费时间(毫秒为单位)
 * @return
 */
public static long getAveCostMillis(){
return costMillisCount/transfromCount;
} /**
 * 构造函数
 * @param sql
 */
public ToPrepareSQL(String sql) {
String noParamSQL = sql.trim();
sqlarr = noParamSQL.toCharArray();
//Log.debug(this.getClass(),"noParamSQL",noParamSQL);
} /**
 * 得到转化的参数SQL语句
 * 
 * @return
 */
public String transform() throws ParseException {
transfromCount++;
long before = System.currentTimeMillis();
if (sqlarr.length < 6) {
throw new ParseException("SQL语句长度太短");
}
String entryWord = new String(sqlarr, 0, 6).toUpperCase();
if (entryWord.equals(supEntryWordArr[0])
|| entryWord.equals(supEntryWordArr[1])
|| entryWord.equals(supEntryWordArr[2])) {// SELECT,UPDATE,DELETE
transformOtherSQL();
} else if (isInsertValuesSQL(new String(sqlarr))) {// INSERT VALUES
transformInsertValuesSQL();
} else if (entryWord.equals(supEntryWordArr[3])) {// INSERT SELECT
transformOtherSQL();
} else {
throw new ParseException("SQL语句出错");
}
long after = System.currentTimeMillis();
long costMillis = after - before;
costMillisCount+=costMillis;

String paramSQL = new String(sqlarr);
//Log.debug(this.getClass(),"paramSQL",paramSQL);
return paramSQL;
} /**
 * 得到参数列表
 * 
 * @return
 */
public Object[] getParams() {
return paramList.toArray();
} /**
 * 判断是否是insert values SQL语句
 * @param sql
 * @return
 */
private boolean isInsertValuesSQL(String sql) {
Matcher m = insValPattern.matcher(sql);
boolean b = m.find();
if (b) {
index = m.end() - 1;
}
return b; } private void transformOtherSQL() throws ParseException { for (index = 0; index < sqlarr.length; index++) { /**
 * 假如当前不是符号,且前面是符号
 */
if (!isSymbol() && preIsSymbol()) {
int startIndex = index;// 数据的起始位置
if (isStringStart()) {// 字符数据
while (!isStringEnd())
;
} else {
while (!isEndPos() && nextChar() != ' '
&& nextChar() != ',' && nextChar() != ')') {
index++;
}
} int endIndex = index;// 数据的结束位置
int length = (endIndex - startIndex) + 1; String param = String.copyValueOf(sqlarr, startIndex, length)
.trim();// 得到参数
boolean isString = false;
if ((isString=isString(param))||isDigit(param)) {
if(isString){
  param = param.substring(1,param.length()-1);//去掉两边的单引号
}

paramList.add(param);// 添加到参数列表
sqlarr[startIndex] = '?';// 设置起始位置的char为参数标识
// 其他为空格
for (int i = startIndex + 1; i < endIndex + 1; i++) {
sqlarr[i] = ' ';
}
}
}
}
} private void transformInsertValuesSQL() throws ParseException {
for (; index < sqlarr.length; index++) { if (sqlarr[index] == '(' || sqlarr[index] == ',') { int startIndex = ++index;// 数据的起始位置 if (isStringStart()) {// 字符数据
while (!isStringEnd())
;
} else {
while (!isEndPos() && nextChar() != ','
&& nextChar() != ')') {
index++;
}
} int endIndex = index;// 数据的结束位置
int length = (endIndex - startIndex) + 1; String param = String.copyValueOf(sqlarr, startIndex, length)
.trim();// 得到参数
boolean isString = false;
if ((isString=isString(param))||isDigit(param)) {
if(isString){
  param = param.substring(1,param.length()-1);//去掉两边的单引号
}
paramList.add(param);// 添加到参数列表
sqlarr[startIndex] = '?';// 设置起始位置的char为参数标识
// 其他为空格
for (int i = startIndex + 1; i < endIndex + 1; i++) {
sqlarr[i] = ' ';
}
}
} }
}

解决方案 »

  1.   

    /**
     * 预先获取后面一个字符,调用这个函数前应该先调用isEnd函数;
     */
    private char nextChar() {
    return sqlarr[index + 1];
    } /**
     * 获取前面一个字符,调用这个函数前应该先调用isStart函数;
     */
    private char previousChar() {
    return sqlarr[index - 1];
    } /**
     * 判断当前字符是否是符号
     */
    private boolean isSymbol() {
    char c = sqlarr[index];
    /**
     * 判断当前是否遇到了 <=,>=,!=,=, <,>
     */
    if (c == '=' || c == '<' || c == '>') {
    return true;
    } else {
    return false;
    }
    } /**
     * 判断前面一个字符是否是符号
     */
    private boolean preIsSymbol() {
    int offset = -1;
    return preIsSymbol(offset);
    } /**
     * 假如前面是空格,那么递规调用自己直至找到符号或者其他字符
     */
    private boolean preIsSymbol(int offset) {
    if ((index + offset) < 0) {
    return false;
    }
    char c = relative(offset);
    /**
     * 判断前面是否遇到了 <=,>=,!=,=, <,>
     */
    if (c == '=' || c == '<' || c == '>') {
    return true;
    } else if (c == ' ') {
    return preIsSymbol(--offset);
    } else {
    return false;
    }
    } /**
     * 判断当前索引所在的位置是否是开始位置
     */
    private boolean isStartPos() {
    return index == 0;
    } /**
     * 判断当前索引所在的位置是否是结束位置
     */
    private boolean isEndPos() {
    return index == sqlarr.length - 1;
    } /**
     * 相对移动
     * 
     * @param offset
     * @return
     */
    private char relative(int offset) {
    return sqlarr[index + offset];
    } private int size() {
    return sqlarr.length;
    } /**
     * 判断是否是字符串的起始位置
     * 
     * @return
     * @throws ParseException
     */
    private boolean isStringStart() throws ParseException {
    while (sqlarr[index] == ' ') {
    index++;
    }
    char c = sqlarr[index];
    if (c == '\''
    && (isStartPos() || previousChar() == ' '
    || previousChar() == ',' || previousChar() == '(' || preIsSymbol())) {
    int sum = 0;
    for (int i = 1; i < size() - index; i++) {// 相对位置递增
    char detect = relative(i);
    if (detect == '\'') {
    sum++;
    } else {
    break;
    }
    } if (sum == 0) {
    return true;
    } else if (sum ==1) {//表示空字符串''
    return true;
    }else if (sum % 2 == 0) {
    index = index + sum;
    return true;
    } else {
    throw new ParseException("解析单引号出错");
    } } else {
    return false;
    }
    } /**
     * 判断是否是字符串的结束位置
     * 
     * @return
     * @throws ParseException
     */
    private boolean isStringEnd() throws ParseException {
    if (isEndPos()) {
    throw new ParseException("缺少右单引号");
    }
    char c = sqlarr[++index];
    if (c == '\''
    && (isEndPos() || nextChar() == ' ' || nextChar() == ',' || nextChar() == ')')
    && (isEndPos() || nextChar() != '\'')) {
    int sum = 0;
    int offset = -1;
    while ((index + offset) >= 0 && relative(offset) == '\'') {
    offset--;
    sum++;
    }
    if (sum == 0) {
    return true;
    } else if (sum ==1) {//表示空字符串''
    return true;
    } else if (sum % 2 == 0) {
    return true;
    } else {
    // 这个地方么说明一下,假如sum为奇数,那么加上当前索引指向的char表明是一个或多个'';
    return false;
    }
    } else {
    return false;
    }
    } /**
     * 判断数值是否是字符串类型
     * 
     * @param value
     * @return
     */
    private boolean isString(String value) {
    if (value.startsWith("\'") && value.endsWith("\'")) {// 字符串类型
    return true;
    } else {
    return false;
    }
    } /**
     * 判断数值是否是数字类型(包括小数)
     * 
     * @param value
     * @return
     */
    private boolean isDigit(String value) {
    Matcher m = digitPattern.matcher(value);
    return m.matches();
    }}
      

  2.   

    public class ParseException extends RuntimeException { public ParseException(String desc){
    super(desc);
    }
    }import java.util.ArrayList;/**
     * 小测试
     */
    public class Test { private static ArrayList sqlList = new ArrayList(); public static void add(String sql) {
    sqlList.add(sql);
    } public static void removeAll() {
    sqlList.clear();
    } public static void transformAll() throws ParseException {
    for (int i = 0; i < sqlList.size(); i++) {
    ToPrepareSQL toPrep = new ToPrepareSQL(sqlList.get(i).toString());
    System.out.println(toPrep.transform());
    Object[] params = toPrep.getParams();
    System.out.print("参数列表:");
    for (int i2 = 0; i2 < params.length; i2++) {
    System.out.print(params[i2] + ",");
    }
    System.out.println();
    System.out.println("----------------------------------------");
    }
    } public static void run() {
    Test.removeAll();
    String[] test = {
    "update \"USER\" set create_date = to_date('2005-09-27 10:03:57', 'yyyy-mm-dd hh24:mi:ss'), dept_id = 1005, email_address = '', expire_date = to_date('2010-09-27 00:00:00', 'yyyy-mm-dd hh24:mi:ss'), if_add_collaboration = 0, last_login_date = to_date('2006-01-12 10:58:52', 'yyyy-mm-dd hh24:mi:ss'), \"LEVEL\" = 1, login_xlt_id = 1, org_id = 1331, password = '', phone = '', staff_code = '4321', state = 'A', state_date = to_date('2006-05-09 10:37:54', 'yyyy-mm-dd hh24:mi:ss'), title = '测试', user_name = '周周', user_type = 'I', xlt_id = 2, if_local_admin = 0, if_filter = 0, real_org_id = 1331, org = 1331 where user_id = '50003234'",
    "update \"USER\" set create_date =''",
    "select sf from \"G$sf_FUNC\" where FUNCTION_NAME='sfsf' and STATE='A'",
    "select id 编号,name 名称 from test",
    "delete from worker where name=' james' and id = 9 and id!=8 and id<=7 and date=sysdate",
    "update test set id=0 where id<100",
    "update test set id=0,name='ja  mes' where id<100",
    "select * from worker = '''''ssss ' ",
    "select * from worker where name = 'jam es'",
    "select * from worker where name = 'jam''es'",
    "select * from worker where name = 'jam es'",
    "select * from worker where name='== james'",
    "select * from worker where name = 'jam '' es' or name like 'a'",
    "select * from worker where name=' james' and id = 9 and id!=8 and id<=7 and date=sysdate",
    "select * from worker where name='''=='' james' or id=0",
    "select * from worker where id=1",
    "insert into mytable values(1,'a','on  ''  e',2,'b','tw  o')",
    "insert into mytable values (1,'a','on  ''  e',2,'b','tw  o')",
    "insert into G$PARAM (PARAM_NAME, PARAM_VALUE, \"DESC\", STATE_DATE, CREATE_DATE, STATE, ORG) values ('INFOSERVER_IP', '111.111.1.15:9008', '服务器地址/端口', sysdate, sysdate, 'A', (select PARAMETER_VALUE from G$PARAM where PARAM_NAME='ORG' and rownum<2));" }; int i = 0;
    for (i = 0; i < test.length; i++) {
    Test.add(test[i]);
    }
    try {
    Test.transformAll();
    } catch (ParseException e) {
    e.printStackTrace();
    } System.out.println("转化次数 : " + ToPrepareSQL.getTransfromCount());
    System.out.println("花费总时间(毫秒) : " + ToPrepareSQL.getCostMillisCount());
    System.out.println("平均花费(毫秒) : " + ToPrepareSQL.getAveCostMillis());
    } public static void run2() {
    String sql = "";
    Test.add(sql); try {
    Test.transformAll();
    } catch (ParseException e) {
    e.printStackTrace();
    } } public static void main(String[] args) {
    Test.run();
    }
    }
      

  3.   

    我自己找到一个漏洞了:ToPrepareSQL里的private static int index = 0;
    应该改为private int index = 0;
      

  4.   

    你们帮我测试测试就可以了,很好用的
    ToPrepareSQL toPrep = new ToPrepareSQL(sql);//sql表示非参数SQL语句
    toPrep.transform());//得到转化的参数SQL语句
    toPrep.getParams();//得到参数列表
      

  5.   

    赞!唯一想不明白的就是为什么要这么做这样效率反而不是更低了么?如果要用动态SQL的 可以这样传啊
    executeQuery(String dynamicSQL, Object[] params)
      

  6.   

    回复楼上的:
     因为我们公司以前用的都是非参数的SQL,现在为了提高效率所以需要把所有的非参数的SQL改为参数SQL,假如一个一个去改的话我觉得实在是太麻烦了,而且耗时;所以我觉得这样做是最好的;还有,在拼凑SQL语句的时候用参数方式也很麻烦不是吗?在我们公司经常用一个函数返回一部分SQL语句,然后经过多次拼凑出来组成一个完整的SQL,如果用参数的话,那么还要把参数列表传来传去,而且如果数值太多的话很容易出错,如果用非参数SQL的话要方便的多而且效率也不算低啊,根据我的测试平均一毫秒左右就能转化成参数SQL;