今天在写底层数据库操做的时候遇到了一个异常,异常的解决办法我实际上早就有了,只是不明白为何为报这个异常,激起我求知的欲望。java
问题描述:项目底层的增长、删除、修改都采用了QueryRunner提供的update()来实现,关键问题就来了,我用拼好的删除语句(deleteSQL)能够完美执行,可是在测试拼接完整的insert插入语句的时候却通不过,控制台输出的信息Wrong number of parameters: expected 3, wasgiven 0。这里我没有采用常规的占位符,而是直接在拼好SQL语句扔进update执行。sql
经过错误提示:能够大胆猜想,多是底层对insert语句进行了特殊处理,把value里面的内容解析为了占位符,或者底层要求insert语句必须采用占位符这种状况。从网上下载相应的源码包,咱们经过源码进入调试一条一条分析源码。数据库
public int update(Connection conn, String sql) throws SQLException { return this.update(conn, false, sql, (Object[]) null); }
能够看到调用update实际上仍是调用带有参数的方法,只是参数为空而已。less
private int update(Connection conn, boolean closeConn, String sql, Object... params) throws SQLException { if (conn == null) { throw new SQLException("Null connection"); } if (sql == null) { if (closeConn) { close(conn); } throw new SQLException("Null SQL statement"); } PreparedStatement stmt = null; int rows = 0; try { stmt = this.prepareStatement(conn, sql); this.fillStatement(stmt, params); rows = stmt.executeUpdate(); } catch (SQLException e) { this.rethrow(e, sql, params); } finally { close(stmt); if (closeConn) { close(conn); } } return rows; }
从这个方法中能够看到关键是try块里面的内容,这里面调用prepareStatement,这一步应该是一个预处理。下面的这个方法应该是关键所在,也就是填充参数的时候。测试
public void fillStatement(PreparedStatement stmt, Object... params) throws SQLException { // check the parameter count, if we can ParameterMetaData pmd = null; if (!pmdKnownBroken) { pmd = stmt.getParameterMetaData(); int stmtCount = pmd.getParameterCount(); int paramsCount = params == null ? 0 : params.length; if (stmtCount != paramsCount) { throw new SQLException("Wrong number of parameters: expected " + stmtCount + ", was given " + paramsCount); } } // nothing to do here if (params == null) { return; } for (int i = 0; i < params.length; i++) { if (params[i] != null) { stmt.setObject(i + 1, params[i]); } else { // VARCHAR works with many drivers regardless // of the actual column type. Oddly, NULL and // OTHER don't work with Oracle's drivers. int sqlType = Types.VARCHAR; if (!pmdKnownBroken) { try { sqlType = pmd.getParameterType(i + 1); } catch (SQLException e) { pmdKnownBroken = true; } } stmt.setNull(i + 1, sqlType); } } }
报错信息正好来至这个方法中,也就是这个stmtCount的值为参数的个数,在SQLServerParameterMetaData类中咱们也看到了以下方法this
private MetaInfo parseStatement(String paramString) /* */ throws SQLServerException /* */ { /* 255 */ StringTokenizer localStringTokenizer = new StringTokenizer(paramString, " "); /* 256 */ if (localStringTokenizer.hasMoreTokens()) /* */ { /* 258 */ String str = localStringTokenizer.nextToken().trim(); /* */ /* 260 */ if (str.equalsIgnoreCase("INSERT")) { /* 261 */ return parseStatement(paramString, "INTO"); /* */ } /* 263 */ if (str.equalsIgnoreCase("UPDATE")) { /* 264 */ return parseStatement(paramString, "UPDATE"); /* */ } /* 266 */ if (str.equalsIgnoreCase("SELECT")) { /* 267 */ return parseStatement(paramString, "FROM"); /* */ } /* 269 */ if (str.equalsIgnoreCase("DELETE")) { /* 270 */ return parseStatement(paramString, "FROM"); /* */ } /* */ } /* 273 */ return null; /* */ }
private MetaInfo parseStatement(String paramString1, String paramString2) /* */ { /* 213 */ StringTokenizer localStringTokenizer = new StringTokenizer(paramString1, " ,", true); /* */ /* 217 */ String str1 = null; /* 218 */ String str2 = ""; /* 219 */ while (localStringTokenizer.hasMoreTokens()) /* */ { /* 221 */ String str3 = localStringTokenizer.nextToken().trim(); /* */ /* 223 */ if (str3.equalsIgnoreCase(paramString2)) /* */ { /* 225 */ if (localStringTokenizer.hasMoreTokens()) /* */ { /* 227 */ str1 = escapeParse(localStringTokenizer, localStringTokenizer.nextToken()); /* 228 */ break; /* */ } /* */ } /* */ } /* */ /* 233 */ if (null != str1) /* */ { /* 235 */ if (paramString2.equalsIgnoreCase("UPDATE")) /* 236 */ str2 = parseColumns(paramString1, "SET"); /* 237 */ else if (paramString2.equalsIgnoreCase("INTO")) /* 238 */ str2 = parseInsertColumns(paramString1, "("); /* */ else { /* 240 */ str2 = parseColumns(paramString1, "WHERE"); /* */ } /* 242 */ return new MetaInfo(str1, str2); /* */ } /* */ /* 245 */ return null; /* */ }
这些方法都会经过预处理SQL语句,而处理insert插入语句的时候,是经过INTO后面的()的内容来判断,而里面放的是插入的列名,各类列的名字确定不一样,因此判定是经过“,”来分割实现统计参数的个数,VALUES(后面的内容在处理的时候其实并无用到,接下来就是参数的补齐,最后加上“)”一个完整的insertSQL语句就处理完成,与?占位符没有多大的关系。那么delete又是怎么可以正确完成的呢?测试后发现delete判断参数的时候是采用判断?个数,但也不全是问号的个数,必须是正确的写法,并且还可以检查列名是否正确,进一步保证了参数个数的正确性。全部用prepareStatement写的都会验证列名是否写正确。spa