首先,请不要关闭它作为什么NullPointerException以及如何解决它的副本.我知道是什么NullPointerException,我知道如何在我自己的代码中解决它,但不是当它被mysql-connector-java-5.1.36-bin.jar抛出时,我无法控制.
我们在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();
Run Code Online (Sandbox Code Playgroud)
res.getByte(8)NullPointerException使用以下调用堆栈导致a :
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版本的源代码,发现了这个(取自这里):
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 }
Run Code Online (Sandbox Code Playgroud)
如您所见,此行发生异常:
} else if ((columnIndex > this.fields.length)) {
Run Code Online (Sandbox Code Playgroud)
这意味着this.fields不知怎的null.
我能找到的最接近的是这个问题,没有答案.
我怀疑问题不在我发布的查询中.Connection由于我们在同一个连接上运行的其他一些语句,实例可能出现了问题.我只能说,我们在执行它并从中读取数据后立即关闭每个语句ResultSet.
编辑(1/19/2017):
我无法在开发环境中重新创建错误.我认为可能是长时间使用相同连接时触发的一些mysql-connector错误.我限制上面的循环一次加载最多6个元素.另外,我们将mysql-connector版本升级到5.1.40.
我们仍然看到NullPointerExceptions ResultSetImpl,但这次是在不同的位置.
堆栈跟踪是:
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 这里和相关的代码是:
5292 // Handles timezone conversion and zero-date behavior
5293
5294 if (checkDateTypes && !this.connection.getNoDatetimeStringSync()) {
5295 switch (metadata.getSQLType()) {
Run Code Online (Sandbox Code Playgroud)
暗示this.connection是null.this.connection是一个类型的实例变量MySQLConnection,它在构造函数中被初始化,ResultSetImpl并且在public void realClose(boolean calledExplicitly)被调用时仅被设置为null (它被调用public void close(),根据源中的文档,在被调用时ResultSet.close()被调用).我们绝对不会ResultSet在阅读之前读取所有数据.
任何想法如何进行?
自从我发布这个问题以来已经很长时间了,我想发布一个答案来描述导致这个棘手问题的确切场景NullPointerException。
我认为这可能会帮助未来遇到如此令人困惑的异常的读者跳出框框思考,因为我几乎有充分的理由怀疑这是一个 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();
Run Code Online (Sandbox Code Playgroud)
问题在于“进行一些涉及使用同一连接从数据库加载其他记录的处理”部分,不幸的是,我没有将其包含在我原来的问题中,因为我认为问题不存在。
放大该部分,我们有:
if (sc != null) {
...
someMethod (conn);
...
}
Run Code Online (Sandbox Code Playgroud)
看起来someMethod像这样:
public void someMethod (Connection conn)
{
...
SomeOtherClass instance = new SomeOtherClass (conn);
...
}
Run Code Online (Sandbox Code Playgroud)
SomeOtherClass看起来像这样(当然我在这里简化了):
public class SomeOtherClass
{
Connection conn;
public SomeOtherClass (Connection conn)
{
this.conn = conn;
}
protected void finalize() throws Throwable
{
if (this.conn != null)
conn.close();
}
}
Run Code Online (Sandbox Code Playgroud)
SomeOtherClass在某些情况下可能会创建自己的数据库连接,但在其他情况下可以接受现有连接,就像我们这里的连接一样。
正如您所看到的,该部分包含一个someMethod接受打开连接作为参数的调用。someMethod将连接传递到 的本地实例SomeOtherClass。SomeOtherClass有一个finalize关闭连接的方法。
现在,someMethod返回后,instance就有资格进行垃圾收集。当它被垃圾收集时,它的finalize方法被垃圾收集器线程调用,从而关闭连接。
现在我们回到 for 循环,它继续使用同一连接执行 SELECT 语句,该连接可能随时被垃圾收集器线程关闭。
如果垃圾收集器线程碰巧关闭连接,而应用程序线程正处于依赖于打开连接的某些 mysql 连接器方法的中间,则NullPointerException可能会发生。
删除该finalize方法解决了该问题。
我们不经常重写finalize类中的方法,这使得定位错误变得非常困难。
| 归档时间: |
|
| 查看次数: |
1745 次 |
| 最近记录: |