11

据我了解,在 Java 中使用 PreparedStatement 是我们可以多次使用它。但是我在使用 Spring JDBC 的 PreparedStatementCreator 时有些困惑。

例如考虑以下代码,

public class SpringTest {

    JdbcTemplate jdbcTemplate; 
    PreparedStatementCreator preparedStatementCreator; 
    ResultSetExtractor<String> resultSetExtractor;

    public SpringTest() throws SQLException {

        jdbcTemplate = new JdbcTemplate(OracleUtil.getDataSource());

        preparedStatementCreator = new PreparedStatementCreator() {
            String query = "select NAME from TABLE1  where ID=?";
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                return connection.prepareStatement(query);
            }
        };

        resultSetExtractor  = new ResultSetExtractor<String>() {
            public String extractData(ResultSet resultSet) throws SQLException,
            DataAccessException {
                if (resultSet.next()) {
                    return resultSet.getString(1);
                }
                return null;
            }
        };
    }
    public String getNameFromId(int id){
        return jdbcTemplate.query(preparedStatementCreator, new Table1Setter(id), resultSetExtractor);
    }

    private static class Table1Setter implements PreparedStatementSetter{

        private int id;
        public Table1Setter(int id) {
            this.id =id;
        }
        @Override
        public void setValues(PreparedStatement preparedStatement) throws SQLException {
            preparedStatement.setInt(1, id);
        }
    }
    public static void main(String[] args) {
        try {
            SpringTest  springTest = new SpringTest();

            for(int i=0;i<10;i++){
                System.out.println(springTest.getNameFromId(i));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

根据这段代码,当我调用 springTest.getNameFromId(int id) 方法时,它会从给定的 id 返回名称,这里我使用 PreparedStatementCreator 创建 PreparedStatement 并使用 PreparedStatementSetter 设置输入参数,并从 ResultSetExtractor 获得结果。但是性能很慢。

在调试并查看 PreparedStatementCreator 和 JdbcTemplate 内部发生的情况后,我知道 PreparedStatementCreator 每次都会创建新的 PreparedStatement ...!!!

每次当我调用方法 jdbcTemplate.query(preparedStatementCreator,preparedStatementSetter,resultSetExtractor) 时,它都会创建新的 PreparedStatement,这会降低性能。

这是使用 PreparedStatementCreator 的正确方法吗?因为在这段代码中我无法重用 PreparedStatement。如果这是使用 PreparedStatementCreator 的正确方法,那么如何从 PreparedStatement 的可重用性中获益?

4

3 回答 3

3

您使用 PreparedStatementCreator 的方式是正确的。

  1. 在每个新事务中,您都应该创建全新的PreparedStatement实例,这绝对是正确的。PreparedStatementCreator主要用于包装代码块以轻松创建PreparedStatement实例,并不是说您应该在每个项目中重新创建新实例。
  2. PreparedStatement主要用于发送模板化和预编译的SQL语句DBMS,为SQL执行节省一些预编译时间。

总而言之,您所做的是正确的。使用PreparedStatement会比Statement有更好的性能。

于 2013-03-05T12:37:23.033 回答
3

Prepared Statements通常由底层连接池缓存,因此您不必担心每次都创建一个新的。

所以我认为你的实际用法是正确的。

JdbcTemplate 在执行后关闭语句,所以如果你真的想重用同一个准备好的语句,你可以代理语句并拦截语句创建器中的 close 方法

例如(未测试,仅作为示例):

public abstract class ReusablePreparedStatementCreator implements PreparedStatementCreator {

    private PreparedStatement statement;

    public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
        if (statement != null)
            return statement;

        PreparedStatement ps = doPreparedStatement(conn);

        ProxyFactory pf = new ProxyFactory(ps);
        MethodInterceptor closeMethodInterceptor = new MethodInterceptor() {

            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                return null;  // don't close statement
            }
        };

        NameMatchMethodPointcutAdvisor closeAdvisor = new NameMatchMethodPointcutAdvisor();
        closeAdvisor.setMappedName("close");
        closeAdvisor.setAdvice(closeMethodInterceptor);
        pf.addAdvisor(closeAdvisor);

        statement = (PreparedStatement) pf.getProxy();

        return statement;       
    }

    public abstract PreparedStatement doPreparedStatement(Connection conn) throws SQLException;

    public void close() {
        try {
            PreparedStatement ps = (PreparedStatement) ((Advised) statement).getTargetSource().getTarget();
            ps.close();
        } catch (Exception e) {
            // handle exception
        }
    }

}
于 2013-03-08T12:48:31.577 回答
2

在调试并查看 PreparedStatementCreator 和 JdbcTemplate 内部发生的情况后,我知道 PreparedStatementCreator 每次都会创建新的 PreparedStatement ...!!!

我不确定为什么会如此令人震惊,因为PreparedStatement每次调用connection.prepareStatement(query);. 如果你想重复使用同一个,那么你不应该创建一个新的。

于 2013-03-04T05:27:16.733 回答