Hello,
I'm working with Apache Ignite. For discovery I'm using TcpDiscoveryJdbcIpFinder (searching other nodes via database). Implementation of TcpDiscoveryJdbcIpFinder is:
@Override public void registerAddresses(Collection<InetSocketAddress> addrs) throws IgniteSpiException {
...
try {
stmtUnreg = conn.prepareStatement(unregAddrQry);
stmtReg = conn.prepareStatement(regAddrQry);
...
stmtUnreg.close();
...
finally {
U.closeQuiet(stmtUnreg);
U.closeQuiet(stmtReg);
Problem here is that PreparedStatement.close is called twice what is a bad thing, but legal according to JavaDoc:
https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#close()Calling the method close on a Statement object that is already closed has no effect.
Unfortunately Ebean PooledConnection wraps PreparedStatement and close cannot be called more than once. This is because of code:
/**
* Return a PreparedStatement back into the cache.
*/
void returnPreparedStatement(ExtendedPreparedStatement pstmt) {
synchronized (pstmtMonitor) {
if (!pstmtCache.returnStatement(pstmt)) {
try {
// Already an entry in the cache with the exact same SQL...
pstmt.closeDestroy();
} catch (SQLException e) {
logger.error("Error closing Pstmt", e);
}
}
}
}
First time PreparedStatement is closed it is returned to pstmtCache (pstmtCache.returnStatement(pstmt) -> true).
Second time it is already in cache (pstmtCache.returnStatement(pstmt) -> false) and therefor it is closed (pstmt.closeDestroy()).
When it is used next time then it is returened from cache (first call) but it is closed, because of the second call and I got runtime exception:
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The statement is closed.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:191)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.checkClosed(SQLServerStatement.java:1073)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.setString(SQLServerPreparedStatement.java:1577)
at org.avaje.datasource.pool.ExtendedPreparedStatement.setString(ExtendedPreparedStatement.java:326)
at org.apache.ignite.spi.discovery.tcp.ipfinder.jdbc.TcpDiscoveryJdbcIpFinder.unregisterAddresses(TcpDiscoveryJdbcIpFinder.java:235)
... 1 more
I know that Apache Ignite TcpDiscoveryJdbcIpFinder code is bad, but it is correct with definition of PreparedStatement.close.
To solve this you should propably add a field closed to ExtendedPreparedStatement and check it in ExtendedPreparedStatement.close method:
public void close() throws SQLException {
if(closed) return; // Calling the method close on a Statement object that is already closed has no effect.
closed=true;
pooledConnection.returnPreparedStatement(this);
}
--
Greetings,
Ryszard Trojnacki