QueryRunner执行update插入语句异常

 今天在写底层数据库操做的时候遇到了一个异常,异常的解决办法我实际上早就有了,只是不明白为何为报这个异常,激起我求知的欲望。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语句测试发现:

这些方法都会经过预处理SQL语句,而处理insert插入语句的时候,是经过INTO后面的()的内容来判断,而里面放的是插入的列名,各类列的名字确定不一样,因此判定是经过“,”来分割实现统计参数的个数,VALUES(后面的内容在处理的时候其实并无用到,接下来就是参数的补齐,最后加上“)”一个完整的insertSQL语句就处理完成,与?占位符没有多大的关系。那么delete又是怎么可以正确完成的呢?测试后发现delete判断参数的时候是采用判断?个数,但也不全是问号的个数,必须是正确的写法,并且还可以检查列名是否正确,进一步保证了参数个数的正确性。全部用prepareStatement写的都会验证列名是否写正确。spa