Повторное использование PreparedStatement несколько раз


97

в случае использования PreparedStatement с одним общим соединением без какого-либо пула, могу ли я воссоздать экземпляр для каждой операции dml / sql, сохраняя мощность подготовленных операторов?

Я имею в виду:

for (int i=0; i<1000; i++) {
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
    preparedStatement.close();
}

вместо того:

PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i=0; i<1000; i++) {
    preparedStatement.clearParameters();
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
}
preparedStatement.close();

Мой вопрос возникает из-за того, что я хочу поместить этот код в многопоточную среду, вы можете мне дать совет? Спасибо


так что ваш запрос sqlне меняется в цикле? если этот запрос не меняется для каждой итерации цикла, то почему вы создаете новый PreparedStatementдля каждой итерации (в первом фрагменте кода)? Есть ли причина для этого?
— Sabir Khan

скажем, если запрос меняется, то все же второй подход лучше, не так ли? Есть недостатки?
— Stunner

Ответы:


144

Второй способ немного более эффективен, но гораздо лучше выполнять их партиями:

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
        }

        statement.executeBatch();
    }
}

Однако вы зависите от реализации драйвера JDBC, сколько пакетов вы можете выполнить одновременно. Например, вы можете захотеть выполнять их каждые 1000 пакетов:

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        int i = 0;

        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
            i++;

            if (i % 1000 == 0 || i == entities.size()) {
                statement.executeBatch(); // Execute every 1000 items.
            }
        }
    }
}

Что касается многопоточных сред, вам не нужно беспокоиться об этом, если вы приобретете и закроете соединение и оператор в кратчайшей возможной области внутри одного и того же блока метода в соответствии с обычной идиомой JDBC с использованием оператора try-with-resources, как показано на выше фрагменты.

Если эти пакеты являются транзакционными, то вы хотите отключить автоматическую фиксацию соединения и фиксировать транзакцию только после завершения всех пакетов. В противном случае это может привести к загрязнению базы данных, когда первая группа пакетов прошла успешно, а вторая - нет.

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (Connection connection = dataSource.getConnection()) {
        connection.setAutoCommit(false);

        try (PreparedStatement statement = connection.prepareStatement(SQL)) {
            // ...

            try {
                connection.commit();
            } catch (SQLException e) {
                connection.rollback();
                throw e;
            }
        }
    }
}

inside the same method block- вы имеете в виду, что каждый поток будет иметь свой собственный стек, и эти соединения и операторы находятся в стеке с одной стороны, а из другого источника данных будет давать каждому новому вызову executeFunction (== каждый поток) отдельный экземпляр соединения. Я вас правильно понял? »
— Pavel_K 05

Я использую первый метод, но при мониторинге в профилировщике SQL я вижу повторяющиеся несколько подготовленных операторов, а не один. Я не мог понять, почему он показывает несколько утверждений. требуется помощь.
— rogue lad

Ваш ответ хорош при условии, что запрос не меняется в цикле ... что, если запрос меняется, например, в моем случае, когда запрос изменен ... я все же предполагаю, что второй подход лучше. пожалуйста, подтвердите
— Stunner

13

Цикл в вашем коде - это всего лишь чрезмерно упрощенный пример, верно?

Было бы лучше создать PreparedStatementтолько один раз и повторно использовать его снова и снова в цикле.

В ситуациях, когда это невозможно (поскольку это слишком усложняет выполнение программы), по-прежнему полезно использовать a PreparedStatement, даже если вы используете его только один раз, потому что работа на стороне сервера (анализ SQL и кеширование выполнения план), все равно будет сокращаться.

Чтобы решить ситуацию, когда вы хотите повторно использовать Java-сторону PreparedStatement, некоторые драйверы JDBC (например, Oracle) имеют функцию кэширования: если вы создадите PreparedStatementдля того же SQL в том же соединении, он даст вам то же самое (кешированный ) пример.

О многопоточности: я не думаю, что соединения JDBC могут быть разделены между несколькими потоками (т. Е. Могут использоваться одновременно несколькими потоками) в любом случае. Каждый поток должен получить собственное соединение из пула, использовать его и снова вернуть в пул.


1
На самом деле соединение имеет свой эксклюзивный поток, и каждый оператор выполняется в нем, но я обращаюсь к этому потоку через открытый стек подготовленных операторов. Таким образом, другие параллельные потоки сначала передают только параметры, необходимые для создания всех подготовленных операторов, но затем они могут изменять параметры одновременно
— Steel Plume
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.