Czy zestawy wyników i instrukcje JDBC muszą być zamykane osobno, mimo że połączenie jest później zamykane?

256

Uważa się, że dobrym nawykiem jest zamykanie wszystkich zasobów JDBC po użyciu. Ale jeśli mam następujący kod, czy konieczne jest zamknięcie zestawu wyników i instrukcji?

Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
    conn = // Retrieve connection
    stmt = conn.prepareStatement(// Some SQL);
    rs = stmt.executeQuery();
} catch(Exception e) {
    // Error Handling
} finally {
    try { if (rs != null) rs.close(); } catch (Exception e) {};
    try { if (stmt != null) stmt.close(); } catch (Exception e) {};
    try { if (conn != null) conn.close(); } catch (Exception e) {};
}

Pytanie brzmi, czy zamknięcie połączenia wykonuje zadanie, czy też pozostawia niektóre zasoby w użyciu.

Zeemee
źródło
Możliwy duplikat zamykania połączeń z bazą danych w Javie
Austin Schäfer

Odpowiedzi:

199

To, co zrobiłeś, to doskonała i bardzo dobra praktyka.

Powodem, dla którego mówię, że jest to dobra praktyka ... Na przykład, jeśli z jakiegoś powodu używasz „prymitywnego” typu puli bazy danych i wywołujesz połączenie connection.close(), połączenie zostanie zwrócone do puli i ResultSet/ Statementnigdy nie zostanie zamknięte, a następnie napotka wiele różnych nowych problemów!

Więc nie zawsze możesz liczyć na connection.close()sprzątanie.

Mam nadzieję, że to pomoże :)

Paweł
źródło
4
... i najbardziej widoczny powód, aby wszystko zamknąć.
Zeemee,
2
Zgadzam się, że dobrą praktyką jest zamykanie zestawów wyników i oświadczeń. Jednak zestawy wyników i instrukcje są zbierane w pamięci - nie pozostają otwarte na zawsze i nie „napotyka się wielu nowych problemów”.
stepanian
3
@Ralph Stevens - Nie możesz na to liczyć. Miałem sytuację, w której sterownik JSBC MSSQL wyciekł z pamięci, ponieważ zestaw wyników nie został zamknięty, nawet po wyrzuceniu elementów bezużytecznych.
Paul
7
@Paul - Ciekawe. To brzmi dla mnie jak wada sterownika JDBC.
stepanian
2
@tleb - działałoby zgodnie z oczekiwaniami. chociaż teoretycznie wyjątki są „drogie”, więc wystąpiłoby bardzo małe pukanie do wydajności (które już zidentyfikowaliście)
Paul
124

Java 1.7 znacznie ułatwia nam życie dzięki instrukcji try-with-resources .

try (Connection connection = dataSource.getConnection();
    Statement statement = connection.createStatement()) {
    try (ResultSet resultSet = statement.executeQuery("some query")) {
        // Do stuff with the result set.
    }
    try (ResultSet resultSet = statement.executeQuery("some query")) {
        // Do more stuff with the second result set.
    }
}

Ta składnia jest dość krótka i elegancka. I connectionrzeczywiście będzie zamknięty, nawet jeśli statementnie da się go stworzyć.

Raúl Salinas-Monteagudo
źródło
56
Nie musisz zagnieżdżać się w ten sposób, możesz zrobić to wszystko w jednej próbie z zasobami, po prostu traktuj deklaracje zasobów jako oddzielne instrukcje (oddzielone przez ;)
Mark Rotteveel
2
Oznacz Rotteveel: możesz użyć jednej próby dla wszystkich trzech połączeń, instrukcji i zestawu wyników, ale jeśli chcesz wykonać kilka zapytań, musisz zamknąć poprzedni zestaw wyników przed rozpoczęciem nowego zapytania. Przynajmniej w taki sposób działał używany przeze mnie DBMS.
Raúl Salinas-Monteagudo
dlaczego nie zrobisz czegoś takiego? try (otwarte połączenie) {try (wiele instrukcji i zestawów wyników) {szczególnie, gdy wyniki kolejnych zapytań mogą być obliczone z poprzednimi.
Daniel Hajduk
Daniel: Kiedy użyłem tego wzorca, bazowy backend JDBC nie obsługiwał otwierania zestawu wyników i otwierania drugiego.
Raúl Salinas-Monteagudo
rascio, możesz zrobić wszystko, czego potrzebujesz w bloku połowu
Raúl Salinas-Monteagudo
73

Z javadocs :

Kiedy Statementobiekt jest zamknięty, jego bieżący ResultSetobiekt, jeśli taki istnieje, również jest zamknięty.

Jednak javadocs nie są bardzo jasne, czy Statementi ResultSetsą zamykane po zamknięciu instrumentu bazowego Connection. Po prostu stwierdzają, że zamknięcie połączenia:

Zwalnia Connectionbazę danych tego obiektu i zasoby JDBC natychmiast, zamiast czekać na automatyczne zwolnienie.

Moim zdaniem, zawsze jawnie zamknij ResultSets, Statementsa Connectionskiedy skończysz z nimi, ponieważ implementacja closemoże się różnić w zależności od sterowników bazy danych.

Możesz zaoszczędzić sobie dużo kodu płyty kotłowej, stosując metody takie jak closeQuietlyw DBUtils firmy Apache.

dogbane
źródło
1
Dzięki, dogbane. Chodzi o to, że nie możesz polegać na implementacji Connection.close, prawda?
Zeemee,
1
uwaga dodatkowa dla n00bs takich jak ja - stackoverflow.com/questions/3992199/what-is-boilerplate-code
David Blaa
39

Teraz używam Oracle z Javą. Oto mój punkt widzenia:

Powinieneś zamknąć ResultSeti Statementwyraźnie, ponieważ Oracle ma problemy z utrzymaniem otwartych kursorów nawet po zamknięciu połączenia. Jeśli nie zamkniesz ResultSet(kursora), pojawi się błąd, taki jak przekroczenie maksymalnej liczby otwartych kursorów .

Myślę, że możesz napotkać ten sam problem z innymi bazami danych, z których korzystasz.

Oto samouczek Zamknij zestaw wyników po zakończeniu :

Zamknij zestaw wyników po zakończeniu

Zamknij ResultSetobiekt, jak tylko skończysz pracę z ResultSetobiektem, mimo że Statementobiekt ResultSetniejawnie zamyka obiekt po zamknięciu, ResultSetjawne zamknięcie daje modułowi odśmiecającemu możliwość jak najszybszego odzyskania pamięci, ponieważ ResultSetobiekt może zajmować dużo pamięci w zależności od zapytania.

ResultSet.close();

niebieskawy
źródło
Dzięki hilal, to dobry powód, aby zamknąć go tak wcześnie, jak to możliwe. Jednak nie ma znaczenia, czy zestaw wyników i instrukcja zostaną zamknięte bezpośrednio przed połączeniem (to znaczy w niektórych przypadkach: nie tak wcześnie, jak to możliwe)?
Zeemee,
Jeśli zamkniesz połączenie, to również zamknie wszystkie instrukcje i
I dlaczego powinienem zamknąć zestaw wyników przed połączeniem? Masz na myśli problemy ze sterownikiem Oracle?
Zeemee,
1
oto bardziej ogólne wyjaśnienie :) stackoverflow.com/questions/103938/…
Teoretycznie, jeśli zamkniesz oświadczenie, nie musisz zamykać zestawów wyników, ale prawdopodobnie jest to dobra praktyka.
rogerdpack
8

Jeśli chcesz mieć bardziej zwarty kod, sugeruję użycie Apache Commons DbUtils . W tym przypadku:

Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
    conn = // Retrieve connection
    stmt = conn.prepareStatement(// Some SQL);
    rs = stmt.executeQuery();
} catch(Exception e) {
    // Error Handling
} finally {
    DbUtils.closeQuietly(rs);
    DbUtils.closeQuietly(stmt);
    DbUtils.closeQuietly(conn);
}
baron5
źródło
3
co się stanie, jeśli użyję tego kodu zamiast rs.close (), stmt.close (), conn.close ()
Onkar Musale
3

Ta poprawna i bezpieczna metoda zamknięcia zasobów powiązanych z JDBC to (zaczerpnięta z Jak poprawnie zamknąć zasoby JDBC - za każdym razem ):

Connection connection = dataSource.getConnection();
try {
    Statement statement = connection.createStatement();

    try {
        ResultSet resultSet = statement.executeQuery("some query");

        try {
            // Do stuff with the result set.
        } finally {
            resultSet.close();
        }
    } finally {
        statement.close();
    }
} finally {
    connection.close();
}
Prasanth Jayachandran
źródło
3

Nie ma znaczenia, czy Connectionmożna je gromadzić czy nie. Nawet połączenie z pulą musi zostać wyczyszczone przed powrotem do basenu.

„Wyczyść” zwykle oznacza zamknięcie zestawów wyników i wycofanie wszelkich oczekujących transakcji, ale nie zamknięcie połączenia. W przeciwnym razie pula traci sens.

Mad Calm
źródło
2

Nie, nie musisz niczego zamykać, ALE połączenie. Zgodnie ze specyfikacją JDBC zamknięcie dowolnego wyższego obiektu spowoduje automatyczne zamknięcie niższych obiektów. Zamknięcie Connectionspowoduje zamknięcie wszystkich Statementutworzonych połączeń. Zamknięcie dowolnego Statementspowoduje zamknięcie wszystkich, ResultSetktóre zostały utworzone przez to Statement. Nie ma znaczenia, czy Connectionmożna je gromadzić czy nie. Nawet połączenie z pulą musi zostać wyczyszczone przed powrotem do basenu.

Oczywiście możesz mieć długie zagnieżdżone pętle na Connectiontworzeniu wielu instrukcji, a następnie zamknięcie ich jest właściwe. Prawie nigdy nie zamykam ResultSet, wydaje się przesadny podczas zamykania Statementlub ConnectionBĘDZIE je zamykać.

Enerccio
źródło
1

Stworzyłem następującą metodę tworzenia One Liner wielokrotnego użytku:

public void oneMethodToCloseThemAll(ResultSet resultSet, Statement statement, Connection connection) {
    if (resultSet != null) {
        try {
            if (!resultSet.isClosed()) {
                resultSet.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (statement != null) {
        try {
            if (!statement.isClosed()) {
                statement.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if (connection != null) {
        try {
            if (!connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Używam tego kodu w klasie nadrzędnej odziedziczonej po wszystkich moich klasach, które wysyłają zapytania DB. Mogę używać Oneliner na wszystkich zapytaniach, nawet jeśli nie mam wynikowego zestawu. Metoda zajmuje się zamykaniem zestawu wyników, instrukcji, połączenia we właściwej kolejności. Tak wygląda mój wreszcie blok.

finally {
    oneMethodToCloseThemAll(resultSet, preStatement, sqlConnection);
}
Springe
źródło
-1

Niektóre funkcje wygody:

public static void silentCloseResultSets(Statement st) {
    try {
        while (!(!st.getMoreResults() && (st.getUpdateCount() == -1))) {}
    } catch (SQLException ignore) {}
}
public static void silentCloseResultSets(Statement ...statements) {
    for (Statement st: statements) silentCloseResultSets(st);
}
Mad Calm
źródło
Nic tu niczego nie zamyka. Tylko bezcelowa pętla, która marnuje odczyt całej odpowiedzi, nawet jeśli wyraźnie nie jest już potrzebna.
Markiz Lorne
-1

W przypadku formularza Java 6 lepiej jest sprawdzić, czy jest zamknięty, czy nie przed zamknięciem (na przykład, jeśli jakiś puli połączeń wyklucza połączenie w innym wątku) - na przykład jakiś problem z siecią - stan instrukcji i zestawu wyników można zamknąć. (nie zdarza się to często, ale miałem ten problem z Oracle i DBCP). Mój wzór jest taki (w starszej składni Java) to:

try {
    //...   
    return resp;
} finally {
    if (rs != null && !rs.isClosed()) {
        try {
            rs.close();
        } catch (Exception e2) { 
            log.warn("Cannot close resultset: " + e2.getMessage());
        }
    }
    if (stmt != null && !stmt.isClosed()) {
        try {
            stmt.close();
        } catch (Exception e2) {
            log.warn("Cannot close statement " + e2.getMessage()); 
        }
    }
    if (con != null && !conn.isClosed()) {
        try {
            con.close();
        } catch (Exception e2) {
            log.warn("Cannot close connection: " + e2.getMessage());
        }
    }
}

Teoretycznie nie jest w 100% idealny, ponieważ między sprawdzaniem stanu zamknięcia a samym zamknięciem jest trochę miejsca na zmianę stanu. W najgorszym przypadku dostaniesz ostrzeżenie na długo. - ale jest mniejsza niż możliwość zmiany stanu w zapytaniach długoterminowych. Używamy tego wzorca w produkcji z obciążeniem „przeciętnym” (150 jednoczesnych użytkowników) i nie mieliśmy z nim problemu - więc nigdy nie wyświetlaj tego komunikatu ostrzegawczego.

Csákány Róbert
źródło
isClosed()Testy nie są potrzebne , ponieważ zamknięcie któregokolwiek z nich, które jest już zamknięte, nie jest możliwe. Co eliminuje problem okna czasowego. Który zostałby również wyeliminowany poprzez utworzenie zmiennych lokalnych Connection, Statementi ResultSet.
Markiz Lorne