首先,请不要将其作为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.connection
是 MySQLConnection
类型的实例变量,在 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/