Spring 5 JdbcTemplate无法重用PreparedStatement?

Jim*_*son 6 java spring jdbc prepared-statement spring-jdbc

SQL 的一项简单优化是重用准备好的语句。您会产生一次解析成本,然后可以PreparedStatement在循环中重用该对象,只需根据需要更改参数即可。Oracle 的 JDBC 教程和许多其他地方都清楚地记录了这一点。

Spring 5 使用时JdbcTemplate似乎使这变得不可能。所有处理 s 的JdbcTemplate查询和更新方法都归结PreparedStatementCreator为一种execute方法。这是该方法的完整代码。

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

“有趣”的部分在finally块中:

        JdbcUtils.closeStatement(ps);
Run Code Online (Sandbox Code Playgroud)

这使得 JdbcTemplate 完全不可能重用准备好的语句。

我已经有很长一段时间(5 年)没有机会使用 Spring JDBC了,但我不记得这曾经是一个问题。我在一个大型 SQL 后端工作,其中有数百条准备好的语句,我清楚地记得不必为每次执行重新准备它们。

我想做的是这样的:

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);
            }
            );
Run Code Online (Sandbox Code Playgroud)

但当然,由于硬编码语句关闭调用,第二次会失败。

问题:假设我想继续使用 Spring JDBC,重用准备好的语句的正确方法是什么?

另外,如果有人知道 Spring 为什么这样做(即有一个很好的理由),我想知道。