Повторное использование 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;
            }
        }
    }
}
BalusC
источник
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