Các kết quả và báo cáo JDBC phải được đóng riêng mặc dù Kết nối được đóng lại sau đó?


256

Nó được cho là một thói quen tốt để đóng tất cả các tài nguyên JDBC sau khi sử dụng. Nhưng nếu tôi có đoạn mã sau, có cần thiết phải đóng Kết quả và Tuyên bố không?

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

Câu hỏi là nếu việc đóng kết nối thực hiện công việc hoặc nếu nó để lại một số tài nguyên được sử dụng.


Câu trả lời:


199

Những gì bạn đã làm là hoàn hảo và thực hành rất tốt.

Lý do tôi nói rằng thông lệ tốt của nó ... Ví dụ: nếu vì lý do nào đó bạn đang sử dụng loại cơ sở dữ liệu "nguyên thủy" và bạn gọi connection.close(), kết nối sẽ được trả về nhóm và ResultSet/ Statementsẽ không bao giờ bị đóng và sau đó bạn sẽ gặp nhiều vấn đề mới khác nhau!

Vì vậy, bạn không thể luôn luôn trông chờ vào connection.close()việc dọn dẹp.

Tôi hi vọng cái này giúp được :)


4
... Và lý do rõ ràng nhất để đóng mọi thứ rõ ràng.
Zeemee

2
Tôi đồng ý rằng đó là cách thực hành tốt để đóng các tập kết quả và báo cáo. Tuy nhiên, tập hợp kết quả và báo cáo là rác được thu thập - chúng không mở mãi mãi và bạn không "gặp phải nhiều vấn đề mới khác nhau".
stepanian

3
@Ralph Stevens - Bạn không thể tin vào điều đó. Tôi đã gặp tình huống trình điều khiển JDBC MSSQL bị rò rỉ bộ nhớ vì các Bộ kết quả không bị đóng, ngay cả sau khi được thu gom rác.
Paul

7
@Paul - Thú vị. Nghe có vẻ như là một thiếu sót của trình điều khiển JDBC.
stepanian

2
@ussyb - điều đó sẽ làm việc như mong đợi. mặc dù về lý thuyết, các trường hợp ngoại lệ là "đắt đỏ" vì vậy sẽ có một tiếng gõ hiệu suất rất nhỏ (mà bạn đã xác định)
Paul

124

Java 1.7 làm cho cuộc sống của chúng ta dễ dàng hơn nhiều nhờ vào câu lệnh try-with-resource .

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

Cú pháp này khá ngắn gọn và thanh lịch. Và connectionthực sự sẽ bị đóng cửa ngay cả khi statementkhông thể được tạo ra.


56
Bạn không cần phải lồng như thế này, bạn có thể thực hiện tất cả trong một lần thử với tài nguyên, chỉ cần coi khai báo tài nguyên là các câu lệnh riêng biệt (cách nhau ;)
Mark Rotteveel

2
Đánh dấu Rotteveel: bạn có thể sử dụng một lần thử cho cả ba Kết nối, Tuyên bố và Kết quả, nhưng nếu bạn muốn thực hiện một số truy vấn, bạn phải đóng Kết quả trước đó trước khi bắt đầu truy vấn mới. Ít nhất đó là cách mà DBMS tôi đang sử dụng hoạt động.
Raúl Salinas-Monteagudo

Tại sao bạn không làm điều gì đó như thế này? thử (kết nối mở) {thử (nhiều câu lệnh & kết quả) {đặc biệt khi các kết quả truy vấn tiếp theo có thể tính toán với các câu hỏi trước.
Daniel Hajduk

Daniel: Khi tôi sử dụng mẫu đó, phần phụ trợ JDBC nằm bên dưới không hỗ trợ giữ Kết quả mở và mở cái thứ hai.
Raúl Salinas-Monteagudo

rascio, bạn có thể làm bất cứ điều gì bạn cần trong khối bắt
Raúl Salinas-Monteagudo

73

Từ javadocs :

Khi một Statementđối tượng được đóng lại, ResultSetđối tượng hiện tại của nó , nếu một đối tượng tồn tại, cũng bị đóng lại.

Tuy nhiên, javadocs không rõ lắm về việc có StatementResultSetđược đóng khi bạn đóng bên dưới không Connection. Họ chỉ đơn giản nói rằng đóng một kết nối:

Phát hành Connectioncơ sở dữ liệu và tài nguyên JDBC của đối tượng này ngay lập tức thay vì chờ chúng được tự động phát hành.

Theo tôi, lúc nào cũng rõ ràng chặt chẽ ResultSets, StatementsConnectionskhi bạn đã kết thúc với họ như việc thực hiện closecó thể khác nhau giữa các trình điều khiển cơ sở dữ liệu.

Bạn có thể tiết kiệm cho mình rất nhiều mã nồi hơi bằng cách sử dụng các phương thức như closeQuietlytrong DBUtils từ Apache.


1
Cảm ơn chó. Vấn đề là bạn không thể phụ thuộc vào việc triển khai Connection.close, phải không?
Zeemee


39

Tôi hiện đang sử dụng Oracle với Java. Đây là quan điểm của tôi:

Bạn nên đóng ResultSetStatementrõ ràng bởi vì Oracle có vấn đề trước đây với việc giữ các con trỏ mở ngay cả sau khi đóng kết nối. Nếu bạn không đóng ResultSet(con trỏ), nó sẽ gây ra lỗi như vượt quá con trỏ mở tối đa .

Tôi nghĩ bạn có thể gặp phải vấn đề tương tự với các cơ sở dữ liệu khác mà bạn sử dụng.

Dưới đây là hướng dẫn Đóng Kết quả khi hoàn thành :

Đóng Kết quả khi hoàn tất

Đóng ResultSetđối tượng ngay khi bạn kết thúc làm việc với ResultSetđối tượng mặc dù Statementđối tượng đóng ResultSethoàn toàn đối tượng khi nó đóng, đóng ResultSetrõ ràng tạo cơ hội cho trình thu gom rác để nhớ lại bộ nhớ càng sớm càng tốt vì ResultSetđối tượng có thể chiếm nhiều bộ nhớ tùy theo truy vấn.

ResultSet.close();


Cảm ơn hilal, đây là những lý do tốt để đóng nó càng sớm càng tốt. Tuy nhiên, có vấn đề gì không nếu Kết quả và Tuyên bố được đóng hoàn toàn trước khi kết nối (điều này có nghĩa là trong một số trường hợp: không sớm nhất có thể)?
Zeemee

Nếu bạn đóng kết nối, nó cũng sẽ đóng tất cả các câu lệnh ans kết quả nhưng bạn nên đóng kết quả trước khi kết nối

Và tại sao tôi nên đóng kết quả trước khi kết nối? Bạn có nghĩa là vì các vấn đề trình điều khiển orory?
Zeemee

1
đây là cách làm rõ chung hơn :) stackoverflow.com/questions/103938/ từ

Về lý thuyết, nếu bạn đóng câu lệnh, bạn không phải đóng kết quả, nhưng có lẽ đó là cách thực hành tốt.
rogerdpack

8

Nếu bạn muốn mã nhỏ gọn hơn, tôi khuyên bạn nên sử dụng Apache Commons DbUtils . Trong trường hợp này:

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

3
Điều gì sẽ xảy ra nếu tôi sử dụng mã này thay vì rs.close (), stmt.close (), Conn.close ()
Onkar Musale

3

Phương thức chính xác và an toàn để đóng các tài nguyên được liên kết với JDBC này (được lấy từ Cách đóng tài nguyên JDBC đúng cách - Mọi lúc ):

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

3

Không quan trọng nếu Connectioncó thể poolable hay không. Ngay cả kết nối poolable cũng phải làm sạch trước khi quay trở lại pool.

"Sạch" thường có nghĩa là đóng kết quả và khôi phục mọi giao dịch đang chờ xử lý nhưng không đóng kết nối. Nếu không thì gộp lại mất ý nghĩa của nó.


2

Không, bạn không cần phải đóng bất cứ thứ gì NHƯNG kết nối. Mỗi thông số kỹ thuật JDBC đóng bất kỳ đối tượng cao hơn sẽ tự động đóng các đối tượng thấp hơn. Đóng Connectionsẽ đóng bất kỳ Statements mà kết nối đã tạo. Đóng bất kỳ Statementsẽ đóng tất cả các ResultSets được tạo bởi đó Statement. Không quan trọng nếu Connectioncó thể poolable hay không. Ngay cả kết nối poolable cũng phải làm sạch trước khi quay trở lại pool.

Tất nhiên bạn có thể có các vòng lặp lồng nhau dài trên việc Connectiontạo ra nhiều câu lệnh, sau đó đóng chúng là thích hợp. Tôi gần như không bao giờ đóng ResultSet, mặc dù có vẻ quá mức khi đóng Statementhoặc ConnectionSILL đóng chúng.


1

Tôi đã tạo Phương thức sau để tạo One liner có thể sử dụng lại:

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

Tôi sử dụng Mã này trong Lớp cha được kế thừa cho tất cả các lớp của tôi gửi Truy vấn DB. Tôi có thể sử dụng Oneliner trên tất cả các Truy vấn, ngay cả khi tôi không có kết quả. Phương thức này đảm nhiệm việc đóng Kết quả, Tuyên bố, Kết nối theo đúng thứ tự. Đây là những gì cuối cùng khối của tôi trông như thế.

finally {
    oneMethodToCloseThemAll(resultSet, preStatement, sqlConnection);
}

-1

Theo như tôi nhớ, trong JDBC hiện tại, các kết quả và câu lệnh triển khai giao diện AutoClosizable. Điều đó có nghĩa là chúng được đóng tự động khi bị phá hủy hoặc đi ra khỏi phạm vi.


3
Không, điều đó chỉ có nghĩa là lệnh closeđược gọi khi kết thúc câu lệnh thử tài nguyên. Xem docs.oracle.com/javase/tutorial/essential/exceptions/ và và docs.oracle.com/javase/8/docs/api/java/lang/AutoClosizable.html .
Zeemee

-1

Một số chức năng tiện lợi:

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

Không có gì ở đây mà đóng cửa bất cứ điều gì. Chỉ là một vòng lặp vô nghĩa mà đọc một cách lãng phí toàn bộ phản hồi, mặc dù rõ ràng nó không muốn nữa.
Hầu tước Lorne

-1

Với dạng Java 6 tôi nghĩ tốt hơn là kiểm tra xem nó có bị đóng hay không trước khi đóng (ví dụ nếu một số pooler kết nối đuổi kết nối trong luồng khác) - ví dụ như một số vấn đề về mạng - trạng thái câu lệnh và kết quả có thể bị đóng. (điều này không thường xuyên xảy ra, nhưng tôi đã gặp vấn đề này với Oracle và DBCP). Mẫu của tôi dành cho điều đó (theo cú pháp Java cũ hơn) là:

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

Về lý thuyết, nó không hoàn hảo 100% bởi vì giữa việc kiểm tra trạng thái đóng và chính nó có một khoảng trống nhỏ để thay đổi trạng thái. Trong trường hợp xấu nhất bạn sẽ nhận được một cảnh báo trong thời gian dài. - nhưng nó ít hơn khả năng thay đổi trạng thái trong các truy vấn dài hạn. Chúng tôi đang sử dụng mô hình này trong sản xuất với tải "avarage" (150 người dùng tương tự) và chúng tôi không gặp vấn đề gì với nó - vì vậy không bao giờ thấy thông báo cảnh báo đó.


Bạn không cần các isClosed()bài kiểm tra, bởi vì việc đóng bất kỳ bài kiểm tra nào đã bị đóng là không có. Mà loại bỏ các vấn đề cửa sổ thời gian. Điều này cũng sẽ được loại bỏ bằng cách thực hiện Connection, Statement, và ResultSetcác biến cục bộ.
Hầu tước Lorne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.