java - ResultSetImpl.checkColumnBounds 或 ResultSetImpl.getStringInternal 中偶尔出现 NullPointerException

标签 java mysql jdbc

首先,请不要将其作为NullPointerException 是什么以及如何修复它的重复项关闭。我知道什么是NullPointerException,我知道如何在我自己的代码中解决它,但是当它被mysql-connector-java-5.1.36-bin.jar 抛出时却不知道strong>,我无法控制。

我们在 mySQL DB 上运行常见的 DB 查询时遇到了异常,该查询大部分时间都有效。我们在部署新版本后开始看到这个异常,但是它发生的查询很长时间没有改变。

查询如下所示(经过一些必要的简化)。我用之前和之后执行的一些逻辑来包围它。实际代码并不是全部在一个方法中,而是我把它放在一个 block 中以便于理解。

Connection conn = ... // the connection is open
...
for (String someID : someIDs) {
    SomeClass sc = null;
    PreparedStatement
        stmt = conn.prepareStatement ("SELECT A, B, C, D, E, F, G, H FROM T WHERE A = ?");
    stmt.setString (1, "someID");
    ResultSet res = stmt.executeQuery ();
    if (res.next ()) {
        sc = new SomeClass ();
        sc.setA (res.getString (1));
        sc.setB (res.getString (2));
        sc.setC (res.getString (3));
        sc.setD (res.getString (4));
        sc.setE (res.getString (5));
        sc.setF (res.getInt (6));
        sc.setG (res.getString (7));
        sc.setH (res.getByte (8)); // the exception is thrown here
    }
    stmt.close ();
    conn.commit ();
    if (sc != null) {
        // do some processing that involves loading other records from the
        // DB using the same connection
    }
}
conn.close();

res.getByte(8) 导致 NullPointerException 具有以下调用堆栈:

com.mysql.jdbc.ResultSetImpl.checkColumnBounds(ResultSetImpl.java:763) com.mysql.jdbc.ResultSetImpl.getStringInternal(ResultSetImpl.java:5251) com.mysql.jdbc.ResultSetImpl.getString(ResultSetImpl.java:5173) com.mysql.jdbc.ResultSetImpl.getByte(ResultSetImpl.java:1650) org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getByte(DelegatingResultSet.java:206) org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getByte(DelegatingResultSet.java:206)

我搜索了相关mysql-connector版本的源码,发现了这个(摘自here):

756 protected final void checkColumnBounds(int columnIndex) throws SQLException {
757     synchronized (checkClosed().getConnectionMutex()) {    
758         if ((columnIndex < 1)) {    
759             throw SQLError.createSQLException(    
760                   Messages.getString("ResultSet.Column_Index_out_of_range_low",    
761                   new Object[] { Integer.valueOf(columnIndex), Integer.valueOf(this.fields.length) }), SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
762                   getExceptionInterceptor());   
763         } else if ((columnIndex > this.fields.length)) {    
764             throw SQLError.createSQLException(    
765                   Messages.getString("ResultSet.Column_Index_out_of_range_high",    
766                   new Object[] { Integer.valueOf(columnIndex), Integer.valueOf(this.fields.length) }), SQLError.SQL_STATE_ILLEGAL_ARGUMENT,   
767                   getExceptionInterceptor());
768         }
769
770         if (this.profileSql || this.useUsageAdvisor) {
771             this.columnUsed[columnIndex - 1] = true;
772         }
773     }
774 }

如您所见,异常发生在这一行:

} else if ((columnIndex > this.fields.length)) {   

这意味着 this.fields 不知何故变成了 null

我能找到的最接近的东西是 this question ,没有答案。

我怀疑问题不在我发布的查询中。 Connection 实例可能出了点问题,因为我们在同一连接上运行了一些其他语句。我只能说,我们在执行后立即关闭每个语句并从其 ResultSet 中读取数据。

编辑(2017 年 1 月 19 日):

我无法在我的开发环境中重新创建错误。我认为这可能是长时间使用同一连接时触发的一些 mysql-connector 错误。我将上述循环限制为一次最多加载 6 个元素。另外,我们将mysql-connector版本升级到了5.1.40。

我们仍然在 ResultSetImpl 中看到 NullPointerException,但这次是在不同的位置。

堆栈跟踪是:

com.mysql.jdbc.ResultSetImpl.getStringInternal(ResultSetImpl.java:5294) com.mysql.jdbc.ResultSetImpl.getString(ResultSetImpl.java:5151) org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getString(DelegatingResultSet.java:198) org.apache.tomcat.dbcp.dbcp2.DelegatingResultSet.getString(DelegatingResultSet.java:198)

这意味着这次异常是由于我们的 res.getString() 调用之一(我不知道是哪一个)而引发的。

我找到了mysql-connector-java-5.1.40-bin.jar的来源here相关代码是:

5292            // Handles timezone conversion and zero-date behavior
5293
5294            if (checkDateTypes && !this.connection.getNoDatetimeStringSync()) {
5295                switch (metadata.getSQLType()) {

这意味着 this.connection 为空。 this.connectionMySQLConnection 类型的实例变量,在 ResultSetImpl 的构造函数中初始化,只有在 时才设置为 null public void realClose(boolean calledExplicitly) 被调用(它由 public void close() 调用,根据源代码中的文档,在 ResultSet.close 时调用() 被调用)。我们绝对不会在读取所有数据之前关闭 ResultSet

任何想法如何进行?

最佳答案

我已经很久没有发布这个问题了,我想发布一个答案来描述导致这个棘手的 NullPointerException 的确切场景。

我认为这可能会帮助遇到这种令人困惑的异常的 future 读者跳出框框思考,因为我几乎完全有理由怀疑这是一个 mysql 连接器错误,即使它毕竟不是。

在调查此异常时,我确信我的应用程序在尝试从中读取数据时不可能关闭数据库连接,因为我的数据库连接不是跨线程共享的,并且如果同一个线程关闭了连接,然后试图访问它,应该抛出一个不同的异常(一些SQLException)。这是我怀疑 mysql 连接器错误的主要原因。

原来毕竟有两个线程访问同一个连接。这很难弄清楚的原因是其中一个线程是垃圾收集器线程

回到我发布的代码:

Connection conn = ... // the connection is open
...
for (String someID : someIDs) {
    SomeClass sc = null;
    PreparedStatement
        stmt = conn.prepareStatement ("SELECT A, B, C, D, E, F, G, H FROM T WHERE A = ?");
    stmt.setString (1, "someID");
    ResultSet res = stmt.executeQuery ();
    if (res.next ()) {
        sc = new SomeClass ();
        sc.setA (res.getString (1));
        sc.setB (res.getString (2));
        sc.setC (res.getString (3));
        sc.setD (res.getString (4));
        sc.setE (res.getString (5));
        sc.setF (res.getInt (6));
        sc.setG (res.getString (7));
        sc.setH (res.getByte (8)); // the exception is thrown here
    }
    stmt.close ();
    conn.commit ();
    if (sc != null) {
        // do some processing that involves loading other records from the
        // DB using the same connection
    }
}
conn.close();

问题在于“进行一些处理,涉及使用同一连接从数据库加载其他记录”部分,不幸的是,我没有将其包含在我的原始问题中,因为我认为问题不是那里。

放大该部分,我们有:

if (sc != null) {
    ...
    someMethod (conn);
    ...
}

someMethod 看起来像这样:

public void someMethod (Connection conn) 
{
    ...
    SomeOtherClass instance = new SomeOtherClass (conn);
    ...
}

SomeOtherClass 看起来像这样(当然我在这里简化了):

public class SomeOtherClass
{
    Connection conn;

    public SomeOtherClass (Connection conn) 
    {
        this.conn = conn;
    }

    protected void finalize() throws Throwable
    { 
        if (this.conn != null)
            conn.close();
    }

}

SomeOtherClass 在某些情况下可能会创建自己的数据库连接,但在其他情况下可以接受现有的连接,例如我们这里的那个。

如您所见,该部分包含对 someMethod 的调用,该调用接受打开的连接作为参数。 someMethod 将连接传递给 SomeOtherClass 的本地实例。 SomeOtherClass 有一个 finalize 方法来关闭连接。

现在,在 someMethod 返回后,instance 就可以进行垃圾回收了。当它被垃圾回收时,它的 finalize 方法被垃圾回收线程调用,从而关闭连接。

现在我们回到 for 循环,它继续使用同一连接执行 SELECT 语句,该连接可能随时被垃圾收集器线程关闭。

如果垃圾收集器线程碰巧关闭了连接,而应用程序线程处于某个依赖于打开连接的 mysql 连接器方法的中间,则可能会发生 NullPointerException

删除 finalize 方法解决了这个问题。

我们通常不会在类中重写 finalize 方法,这使得定位错误非常困难。

关于java - ResultSetImpl.checkColumnBounds 或 ResultSetImpl.getStringInternal 中偶尔出现 NullPointerException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41616564/

相关文章:

java - java中 new String ("ONE") 和 String one = "ONE"有什么区别?

MySQL 左连接使用默认值

mysql - 数据库设计,如何减少不同列的数据冗余

mysql - 如何知道循环的最后位置?

java - 无法使用 DriverManager 连接到 mysql 数据库

java - 本地 H2 服务器 + hibernate : existing database is not visible

java - idl2java 不适用于 64 位 JVM?加载 jre\bin\server\jvm.dll 时出错

java - eclipseoxy中的 cucumber eclipse插件

java - Jboss中的spring boot返回错误404

jdbc - 无法从groovy连接到oracle数据库