在生产中,我们看到Spring Framework(Spring-jdbc 4.2.4)没有清理它存储在本地线程中的数据库连接。
相同线程的后续使用导致jdbcTemplate从threadlocal获得可能过时的连接(一个终止了我的mysql服务器) 而不是从连接池中获取新的。发生这种情况时,我们会在连接上出现Broken pipe或通信链路故障。
WrappedException: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 80,416 milliseconds ago. The last packet sent successfully to
the server was 1 milliseconds ago. [source:]
or
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1038)
at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3621)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2429)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2594)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2541)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2499)
at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1432)
at com.mysql.jdbc.ConnectionImpl.isReadOnly(ConnectionImpl.java:3617)
... 336 more
Caused by: java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
at java.net.SocketOutputStream.write(SocketOutputStream.java:153)
使用以下代码
可以在单个线程上轻松复制问题在mysql服务器上设置连接超时,比如60秒
mysql>设置全局wait_timeout = 60
在java代码中
mainMethod() {
try {
method1() ;
} catch(Exception e) {
method2() // this is the real call
sleep(80secs)
method2() // this is there to a simulate another request on same thread
}
}
@Transactional
method1() {
jdbcTemplate.execute("select 1") ;
throw new RuntimeException() ;
}
@Transactional
method2() {
jdbcTemplate.execute("select 1") ;
}
第二次调用method2是在第一次调用method2泄漏数据连接到threadLocal之后在同一个线程上模拟另一个请求
在调试代码时,我观察到jdbcTemplate调用DataSourceUtils.getConnection来获取连接。
DataSourceUtils.getConnection首先尝试从ThreadLocal获取连接。它只在那里进入dataSource 在threadLocal中没有连接。在这种情况下,它会在threadLocal中找到一个终止(由于超时)连接,这会导致Method2 使用jdbc Broken管道插座异常失败。
将method2更改为@Transactional(propagation = Requires_NEW)没有帮助。
这是一个SpringFramework错误还是应该以不同的方式做某事?应该如何完成method2(对method2的第一次调用),以便在threadLocal上清除其数据库连接