Java Spring5JDBCTemplate无法重用PreparedStatement?

Java Spring5JDBCTemplate无法重用PreparedStatement?,java,spring,jdbc,prepared-statement,spring-jdbc,Java,Spring,Jdbc,Prepared Statement,Spring Jdbc,SQL的一个简单优化是重用准备好的语句。您需要一次解析成本,然后可以在循环中重用PreparedStatement对象,只需根据需要更改参数。这在许多其他地方都有明确的记录 Spring5在使用JdbcTemplate时,似乎不可能做到这一点。所有处理PreparedStatementCreators的JdbcTemplate查询和更新方法都会向下扩展到一个execute方法。下面是该方法的全部代码 public <T> T execute(PreparedStatementCrea

SQL的一个简单优化是重用准备好的语句。您需要一次解析成本,然后可以在循环中重用
PreparedStatement
对象,只需根据需要更改参数。这在许多其他地方都有明确的记录

Spring5在使用
JdbcTemplate
时,似乎不可能做到这一点。所有处理
PreparedStatementCreator
s的
JdbcTemplate
查询和更新方法都会向下扩展到一个
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());
    }
}
这使得使用JdbcTemplate重用准备好的语句完全不可能

我已经有很长一段时间(5年)没有机会使用SpringJDBC了,但我不记得这是一个问题。我在一个大型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);
            }
            );
private static final String sqlGetPDFFile=“从PDFFile中选择id、根目录、文件路径、文件时间、文件大小,其中摘要=?”;
私人编制的PSGetPffile报表;
@自动连线
public void setDataSource(DataSource DataSource)引发SQLException
{
Connection con=dataSource.getConnection();
psGetPdfile=con.prepareStatement(sqlGetPdfile);
this.tmpl=新的JdbcTemplate(数据源);
}
...
...
列表文件=
tmpl.query(
//PreparedStatementCreator
c->{
psGetPdfile.setBytes(1,文件摘要);
返回psGetPDFFile;
},
//行映射器
(rs,n)->
{
long id=rs.getLong(1);
Path rootDir=Path.get(rs.getString(2));
Path filePath=Path.get(rs.getString(3));
FileTime FileTime=FileTime.from(rs.getTimestamp(4.toInstant());
long fileSize=rs.getLong(5);
返回新的PDFFile(id、fileDigest、rootDir、filePath、fileTime、fileSize);
}
);
当然,由于硬编码语句close调用,第二次失败

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


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

当然可以,它被称为批处理,您可以通过调用
JdbcTemplate
上的
batchUpdate()
方法之一来实现。在网上搜索,你会发现无数的例子。这并不是我想要的。这不是一个大批量的问题,这是在一个有UI的程序中。你的意思是,在非批处理场景中,准备语句的成本不足以让人担心吗?正确。一些JDBC驱动程序将缓存准备好的SQL语句以供重用,因此当您重新准备SQL时,它不必再次将其发送到数据库服务器进行解析。例如,具有连接属性
prepareThreshold
(默认值:5)、
preparedStatementCacheQueries
(默认值:256)和
preparedStatementCacheSizeMiB
(默认值:5)。第一个“确定切换到使用服务器端准备语句之前所需的
PreparedStatement
执行次数”。PreparedStatement的不重用对于JdbcTemplate来说并不是唯一的。最可能的原因是必须实现PreparedStatement以覆盖绑定变量,但重用将引入游标泄漏的可能问题(即打开的游标太多)。此外,这种JDBC框架仍然比ORM“更好”,比普通JDBC“更差”(由于光标缓存),没有太大的改变压力。@MarmiteBomber这似乎是最好的答案,请将其作为答案而不是评论发布。是的,我发现
JdbcTemplate
在易用性和功能性之间取得了正确的平衡。ORM太重了,而普通的JDBC是个麻烦:-)
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);
            }
            );