web-dev-qa-db-ja.com

Spring JDBCのPreparedStatementCreatorを使用する適切な方法は何ですか?

私の理解によると、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を再利用できないためです。そして、これがPreparedStatementの再利用性のメリットを享受する方法よりも、PreparedStatementCreatorを使用する正しい方法である場合はどうでしょうか。

10
Jignesh Dhua

プリペアドステートメントは通常、基盤となる接続プールによってキャッシュされるため、毎回新しいステートメントを作成するかどうかを心配する必要はありません。

ですから、実際の使い方は正しいと思います。

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
        }
    }

}
2

あなたはPreparedStatementCreatorを使用する正しい方法にいます。

  1. 新しいトランザクションごとに、新しいPreparedStatementインスタンスを作成する必要があります。これは間違いなく正しいです。 PreparedStatementCreatorは、主にコードブロックをラップして作成するように設計されていますPreparedStatement インスタンスは簡単です。各itmeで新しいインスタンスを再起動する必要があるとは言わないでください。
  2. PreparedStatementは、主に、テンプレート化およびプリコンパイルされたSQLステートメントDBMSを送信するように設計されています。これにより、SQL実行のためのプリコンパイルされた時間が節約されます。

要約すると、あなたがしたことは正しいです。 PreparedStatementを使用すると、Statement

3
Henry Leu

PreparedStatementCreatorとJdbcTemplateの内部で何が起こっているかをデバッグして調べた後、PreparedStatementCreatorが新しいPreparedStatementを毎回作成することを知りました... !!!

connection.prepareStatement(query);を呼び出すたびに新しいPreparedStatementを作成するのは独自のコードであるため、なぜそれがそれほど衝撃的であるのかわかりません。同じものを再利用したい場合は、新しいものを作成しないでください。

1
Ryan Stewart