Wielokrotne użycie PreparedStatement

97

w przypadku korzystania z PreparedStatement z jednym wspólnym połączeniem bez puli, czy mogę odtworzyć instancję dla każdej operacji dml / sql, zachowując moc przygotowanych instrukcji?

Mam na myśli:

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

zamiast:

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

moje pytanie wynika z faktu, że chcę umieścić ten kod w środowisku wielowątkowym, czy możesz dać mi jakąś radę? dzięki

Stalowy pióropusz
źródło
więc twoje zapytanie sqlnie zmienia się z w pętli? jeśli to zapytanie nie zmienia się dla każdej iteracji pętli, to dlaczego tworzysz nowe PreparedStatementdla każdej iteracji (w pierwszym fragmencie kodu)? Czy jest jakiś powód, aby to robić?
Sabir Khan
powiedzmy, że jeśli zapytanie się zmienia, to nadal drugie podejście jest lepsze, prawda? Jakieś wady?
Stunner

Odpowiedzi:

144

Drugi sposób jest odrobinę bardziej wydajny, ale znacznie lepszym sposobem jest wykonywanie ich partiami:

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();
    }
}

Jesteś jednak zależny od implementacji sterownika JDBC, ile partii można wykonać naraz. Możesz na przykład chcieć wykonywać je co 1000 partii:

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

Jeśli chodzi o środowiska wielowątkowe, nie musisz się tym martwić, jeśli uzyskasz i zamkniesz połączenie i instrukcję w najkrótszym możliwym zakresie w tym samym bloku metody zgodnie z normalnym idiomem JDBC przy użyciu instrukcji try-with-resources , jak pokazano w powyżej fragmentów.

Jeśli te partie są transakcyjne, chcesz wyłączyć automatyczne zatwierdzanie połączenia i zatwierdzać transakcję dopiero po zakończeniu wszystkich partii. W przeciwnym razie może to spowodować brudną bazę danych, gdy pierwsza seria partii zakończy się powodzeniem, a później nie.

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
źródło
inside the same method block- masz na myśli, że każdy wątek będzie miał swój własny stos, a te połączenia i instrukcje są na stosie z jednej strony iz innego źródła danych, dadzą każdemu nowemu wywołaniu funkcji executeFunction (== każdy wątek) oddzielne wystąpienie połączenia. Czy dobrze cię rozumiem? ”
Pavel_K
Robię pierwszą metodę, ale podczas monitorowania w profilerze SQL widzę powtarzające się wiele przygotowanych instrukcji, a nie jedną. Nie mogłem zrozumieć, dlaczego wyświetla wiele stwierdzeń. wymagana pomoc.
nieuczciwy chłopak
Twoja odpowiedź jest dobra, pod warunkiem, że zapytanie nie zmienia się w pętli .. co, jeśli zapytanie się zmienia, np. W moim przypadku, gdy zapytanie jest zmienione .. zakładam, że nadal drugie podejście jest lepsze. proszę potwierdzić
Stunner
13

Pętla w twoim kodzie to tylko nadmiernie uproszczony przykład, prawda?

Lepiej byłoby utworzyć PreparedStatementjedyny raz i używać go wielokrotnie w pętli.

W sytuacjach, w których nie jest to możliwe (ponieważ zbytnio skomplikowało to przepływ programu), nadal korzystne jest użycie PreparedStatement, nawet jeśli używasz go tylko raz, ponieważ praca po stronie serwera (parsowanie kodu SQL i buforowanie wykonania plan), nadal będą zmniejszane.

Aby rozwiązać problem, w którym chcesz ponownie użyć strony Java PreparedStatement, niektóre sterowniki JDBC (takie jak Oracle) mają funkcję buforowania: Jeśli utworzysz PreparedStatementdla tego samego SQL w tym samym połączeniu, da ci to samo (buforowane ) instancja.

O wielowątkowości: i tak nie sądzę, aby połączenia JDBC mogły być współużytkowane przez wiele wątków (tj. Używane jednocześnie przez wiele wątków). Każdy wątek powinien otrzymać własne połączenie z puli, użyć go i ponownie zwrócić do puli.

Thilo
źródło
1
W rzeczywistości połączenie ma swój wyłączny wątek i każda instrukcja jest w nim wykonywana, ale mam dostęp do tego wątku za pośrednictwem ujawnionego stosu przygotowanych instrukcji. Tak więc inne współbieżne wątki początkowo przekazują tylko parametry potrzebne do zbudowania wszystkich przygotowanych instrukcji, ale potem mogą jednocześnie modyfikować parametry
Steel Plume