One simple optimization for SQL is the reuse of prepared statements. You incur the parsing cost once and can then reuse the PreparedStatement object within a loop, just changing the parameters as needed. This is clearly documented in Oracle's JDBC tutorial and many other places.
Spring 5 when using JdbcTemplate seems to make this impossible. All JdbcTemplate query and update methods that deal with PreparedStatementCreators funnel down to one execute method. Here's the code of that method in its entirety.
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
applyStatementSettings(ps);
T result = action.doInPreparedStatement(ps);
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("PreparedStatementCallback", sql, ex);
}
finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
The "interesting" bit is in the finally block:
JdbcUtils.closeStatement(ps);
This makes it completely impossible to reuse a prepared statement with JdbcTemplate.
It's been a long time (5 years) since I've had occasion to work with Spring JDBC, but I don't recall this ever being a problem. I worked on a large SQL backend with literally hundreds of prepared statements and I clearly remember not having to re-prepare them for every execution.
What I want to do is this:
private static final String sqlGetPDFFile = "select id,root_dir,file_path,file_time,file_size from PDFFile where digest=?";
private PreparedStatement psGetPDFFile;
@Autowired
public void setDataSource(DataSource dataSource) throws SQLException
{
Connection con = dataSource.getConnection();
psGetPDFFile = con.prepareStatement(sqlGetPDFFile);
this.tmpl = new JdbcTemplate(dataSource);
}
...
...
List<PDFFile> files =
tmpl.query(
// PreparedStatementCreator
c -> {
psGetPDFFile.setBytes(1, fileDigest);
return psGetPDFFile;
},
// RowMapper
(rs, n)->
{
long id = rs.getLong(1);
Path rootDir = Paths.get(rs.getString(2));
Path filePath = Paths.get(rs.getString(3));
FileTime fileTime = FileTime.from(rs.getTimestamp(4).toInstant());
long fileSize = rs.getLong(5);
return new PDFFile(id,fileDigest,rootDir,filePath,fileTime,fileSize);
}
);
But of course this fails the second time because of the hardcoded statement close call.
The question: Assuming I want to continue using Spring JDBC, what is the correct way to reuse prepared statements?
Also, if anyone knows why Spring does this (i.e. there's a good reason for it) I'd like to know.