首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 数据库 > 其他数据库 >

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

2013-10-31 
MySQL JDBC 5.1.25的一个坑(应该算是BUG)这是公司的一个重要项目中的真实案例(目前还未证实其它版本是否存

MySQL JDBC 5.1.25的一个坑(应该算是BUG)

这是公司的一个重要项目中的真实案例(目前还未证实其它版本是否存在,不过刚看了最新版5.1 .26版本还是没有修复这个操作方式,不过用的小伙伴们要注意了哦):


什么样的情况呢,当在代码中使用connection.close()方法的时候,神奇般的StackOverflow了!没错,这就是JDBC自己导致的死递归,堆栈输出的内容如下所示:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

这个堆栈信息可以:

    ConnectionImpl.realClose()

-> ConnectionImpl.closeAllOpenStatements()

->StatementImpl.realClose()

->ResultSetImpl.close()

->ResultSetImpl.realClose()

->RowDataDynamic.close()

->StatementImpl.executeSimpleNonQuery()

->ConnectionImpl.execSQL()

->ConnectionImpl.cleanup()

->ConnectionImpl.realClose()//到这里回来了


这是多么神奇的事情啊,MySQL的JDBC发布的时候难道也没有测试下,但是这种情况据同事介绍也不是每次都会发生,是偶然性情况。于是就要跟一下内在代码是怎么回事。

首先在MySQL JDBC现在的代码中,Connection接口是通过它内部的一个com.mysql.jdbc.ConnectionImpl的实现类来实现的,因此要跟踪close方法,就跟踪它就好了,如下图:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

这里进入正轨,realClose方法进去了,这个方法很长,里面涉及到一些判定是否已经关闭、回滚、io关闭等等操作,在io关闭操作之前,需要关闭被打开的Statement信息,换句话说,MySQL在调用Connection.close的时候会自动关闭掉Statement信息,而无需业务代码来编写,不过你也写了也没错,其余代码我们忽略掉,因为不是问题的重点,关键是它内部确实调用了一个这样的方法,如下图所示:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)


在这个方法里面,会做什么动作呢?简单来说,就是循环,并调用对应的statement的close()方法

MySQL JDBC 5.1.25的一个坑(应当算是BUG)


注意这里传入了两个参数,分别是false、true,第二个参数对这个问题是有用途的,第二个参数代表是否关闭掉Statement下面的ResultSet,在StatementImpl类具体的实现方法中的部分是:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

第一个close自然是close掉当前的ResetSet、第二个是要获取generatedKey的结果集,最后一个closeAllOpenResults是会关闭掉内部记录的一个Set列表,这个列表会在获取GeneratedKeys、getMoreResults(java.sql.Statement.KEEP_CURRENT_RESULT)是增加ResultSet进去。


我们这里主要关心第一个,就是ResultSet的close方法,它ResultSetImpl实现类的close方法,如下所示:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

这里依然调用了ResultSet的realClose方法,和日志中输出的内容一致,这个方法的finally部分会调用一个叫做rowData.close()方法:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

它的类型是:RowData,是MySQL的一个接口:com.mysql.jdbc.RowData,它的实现类有:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

具体使用哪一个,会由一些参数来决定,这个说起来又会涉及到许多源码,与本问题关系不大,暂时不扯开,从堆栈输出中可以看到使用的是第二个RowDataDynamic这个实现类,于是乎打开这个close方法的代码来看看,也很长,不过我们关注关键的部分,那就是它还调用了statement,如下图所示:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

这里关闭的时候其实要执行一条语句,它创建了一个Statement,没有什么问题,因为这个Statement和当前ResultSet的Statement不是同一个(也就是这个Statement可能不是用的RowDataDynamic),但是它调用了一个executeSimpleNonQuery,这个方法需要传入Connection对象,显然一个Connection下面不论多少个Statement,这个Connection都是同一个。这个方法内部做了什么呢?

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

这个代码显然调用了connection的一个executeSQL方法(注意了,这里的MySQLConnection其实是一个接口,是MySQL自己继承于java.sql.Connection的接口,ConnectionImpl也是实现这个接口的,对象始终是同一个)。

接下来又回到ConnectionImpl的execSQL方法里面了,这段代码在内部的抛出异常的时候,且highAvailabilityAsBoolean为false的时候,会调用cleanup方法(默认为false,只有设置了autoReconnect才会变成true,这个参数在初始化Connection的时候被赋值):

MySQL JDBC 5.1.25的一个坑(应当算是BUG)


这里只有抛出异常的时候会到这个里面来,但是异常确实发生了,而且这种发生往往是偶然的,而且一旦偶然发生,将一发不可收拾(例如网络闪了,或服务器端做了什么kill之类的操作),这个cleanup方法内部就会再次调用realClose方法:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

显然,这里的Connection还没有关闭完,所以io不会为空,而且isClose也会返回false,自然会调用realClose方法,这个方法就回到前面的第二幅图的代码了,就这样,程序一发不可收的开始递归了。

使用类似代码的童鞋要注意了,换下版本就好,其余的版本还没看过代码,5.1.16是没有问题,打算抽时间看看5.1.26是否已经修复。

版本中5.1.16中在RowDataDynamic的close()方法也同样调用了realClose,里面并没有调用ConnectionImpl来操作,而是直接用本地的一个ResultSet将其关闭掉了:

MySQL JDBC 5.1.25的一个坑(应当算是BUG)

另外,需要注意的是,其实StackOverflow往往没有平时Demo演示的那么简单,往往经过复杂的嵌套逻辑,以及希望大量的代码复用,在一些偶然的逻辑结构下导致递归起来,而且这种偶然一旦发生可能就形成一种必然了。

最后,小伙伴们不要认为最新的东西就是最好的哦,哈哈!


热点排行