QueryRunner执行update插入语句异常

 今天在写底层数据库操作的时候遇到了一个异常,异常的解决办法我实际上早就有了,只是不明白为什么为报这个异常,激起我求知的欲望。

问题描述:项目底层的增加、删除、修改都采用了QueryRunner提供的update()来实现,关键问题就来了,我用拼好的删除语句(deleteSQL)可以完美执行,但是在测试拼接完整的insert插入语句的时候却通不过,控制台输出的信息Wrong number of parameters: expected 3, wasgiven 0这里我没有采用常规的占位符,而是直接在拼好SQL语句扔进update执行。

通过错误提示:可以大胆猜测,可能是底层对insert语句进行了特殊处理,把value里面的内容解析为了占位符,或者底层要求insert语句必须采用占位符这种情况。从网上下载相应的源码包,我们通过源码进入调试一条一条分析源码。

 public int update(Connection conn, String sql) throws SQLException {
        return this.update(conn, false, sql, (Object[]) null);
    }

可以看到调用update实际上还是调用带有参数的方法,只是参数为空而已。


 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类中我们也看到了如下方法

 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写的都会验证列名是否写正确。



  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: queryrunner.update是一个Java类中的方法,用于执行SQL语句的更新操作。它可以更新数据库中的数据,例如插入、修改或删除数据。在使用该方法时,需要传入一个SQL语句作为参数,该语句将被执行以更新数据库中的数据。 ### 回答2: queryrunner.update是Java中的一个工具类,用于执行一个SQL语句并且将相应的参数传递到SQL语句中。queryrunner.update的作用是在数据库中进行增、删、改的操作。 queryrunner.update执行SQL语句时,可以接受SQL语句的参数。这些参数可以是java.lang.Object的数组,或者也可以是单个的java.lang.Object对象,用于填充SQL语句中的占位符。一般情况下,该方法通常用于执行不返回任何结果集的SQL语句,如INSERT、DELETE、UPDATE等操作。 queryrunner.update方法的使用非常方便和灵活,只需要传递一个连接对象和一个包含SQL语句及其相关参数的数组或对象即可执行SQL语句。同时,该方法还可以接受一个实现了Callback接口的对象作为参数,用于处理SQL执行结果。 在使用queryrunner.update方法时,需要注意一些细节问题。首先,需要确保传入的参数类型与数据库中定义的数据类型相匹配,否则会抛出异常。其次,需要注意保证SQL语句的语法正确,在执行SQL语句之前,需要先对SQL语句进行合法性检查以避免出现错误。 综上所述,queryrunner.update是Java中非常实用的数据库操作工具类,用于执行不返回结果集的SQL语句,并且支持参数绑定和回调函数处理结果。使用queryrunner.update可以方便地进行增、删、改数据库的操作,极大地提高了数据库访问的效率和方便性。 ### 回答3: QueryRunner是Apache Commons DbUtils库中的一个工具类,简化了使用jdbc进行数据库操作的使用成本,其中queryrunner.update()是它的一个方法,用于执行更改操作(比如insert, update, delete)。 在queryrunner.update()中,需要传入3个参数: 1. Connection对象:与数据库建立的连接对象,可以使用dbutils自带的DataSourceUtils获取连接池,也可以使用自己定义的连接池。 2. SQL语句:需要执行的SQL语句,可以是任何DML语句(insert, update, delete)。 3. Object数组:SQL语句中占位符对应的值。严格按照占位符的位置和类型传入对应的值,可以是任何类型的Java数据类型,包括字符串、数字、日期、BLOB等等。 queryrunner.update()的返回值为int类型,代表了这个SQL语句对数据库所做的更改的行数,如果返回值为0,表示没有更改任何行。 需要注意的是,在执行queryrunner.update()之前,必须确保连接对象已经创建,并且在使用之后必须关闭连接,否则会导致数据库连接资源的浪费。 总之,queryrunner.update()是一个很重要的方法,可以帮助我们使用简单的方式执行数据库的更改操作,提高了我们的开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值