JDBC Resultsets and Statement ต้องปิดแยกต่างหากแม้ว่าการเชื่อมต่อจะปิดหลังจากนั้นหรือไม่?


256

มันบอกว่าเป็นนิสัยที่ดีในการปิดทรัพยากร JDBC ทั้งหมดหลังจากการใช้งาน แต่ถ้าฉันมีรหัสต่อไปนี้จำเป็นต้องปิด Resultset และ Statement หรือไม่?

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

คำถามคือถ้าการปิดการเชื่อมต่อทำงานหรือปล่อยให้ทรัพยากรบางอย่างใช้งานอยู่


อาจเป็นไปได้ซ้ำกับการปิดการเชื่อมต่อฐานข้อมูลใน Java
Austin Schäfer

คำตอบ:


199

สิ่งที่คุณทำคือการฝึกฝนที่สมบูรณ์แบบและดีมาก

เหตุผลที่ฉันพูดถึงแนวปฏิบัติที่ดี ... ตัวอย่างเช่นถ้าด้วยเหตุผลบางอย่างที่คุณใช้ในการรวมฐานข้อมูลประเภท "ดั้งเดิม" และคุณเรียกconnection.close()การเชื่อมต่อจะถูกส่งกลับไปยังกลุ่มและResultSet/ Statementจะไม่ถูกปิดและจากนั้นคุณ จะพบกับปัญหาใหม่มากมาย!

ดังนั้นคุณไม่สามารถconnection.close()คาดหวังในการทำความสะอาด

ฉันหวังว่านี่จะช่วยได้ :)


4
... และเหตุผลที่ชัดเจนที่สุดที่จะปิดทุกอย่างอย่างชัดเจน
Zeemee

2
ฉันยอมรับว่าเป็นวิธีปฏิบัติที่ดีในการปิดชุดผลลัพธ์และข้อความสั่ง อย่างไรก็ตามชุดผลลัพธ์และคำสั่งเป็นการรวบรวมขยะ - พวกเขาไม่เปิดตลอดไปและคุณไม่ "พบปัญหาใหม่หลายอย่าง"
stepanian

3
@Ralph Stevens - คุณไม่สามารถนับได้ ฉันมีสถานการณ์ที่ไดรเวอร์ MSSQL JDBC รั่วหน่วยความจำเนื่องจาก ResultSet ไม่ได้ถูกปิดแม้ว่าจะถูกเก็บรวบรวมขยะ
พอล

7
@Paul - น่าสนใจ ฟังดูแล้วว่าฉันชอบข้อบกพร่องของไดรเวอร์ JDBC
stepanian

2
@tleb - จะทำงานได้ตามที่คาดไว้ แม้ว่าในทางทฤษฎีแล้วข้อยกเว้นจะ "แพง" ดังนั้นจะมีผลการปฏิบัติงานที่เล็กน้อยมาก (ซึ่งคุณได้ระบุไว้แล้ว)
Paul

124

Java 1.7 ทำให้ขอบคุณชีวิตของเราง่ายมากที่จะลองคำสั่งที่มีทรัพยากร

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

ไวยากรณ์นี้ค่อนข้างสั้นและสง่างาม และconnectionจะถูกปิดอย่างแน่นอนแม้ว่าstatementจะไม่สามารถสร้างได้


56
คุณไม่จำเป็นต้องทำเช่นนี้คุณสามารถทำมันทั้งหมดด้วยการลองกับแหล่งข้อมูลเพียงทำตามการประกาศทรัพยากรเป็นคำสั่งแยกต่างหาก (คั่นด้วย;)
Mark Rotteveel

2
ทำเครื่องหมาย Rotteveel: คุณสามารถใช้การลองครั้งเดียวสำหรับทั้งสามการเชื่อมต่อชุดคำสั่งและชุดผลลัพธ์ แต่ถ้าคุณต้องการดำเนินการหลายแบบสอบถามคุณต้องปิดชุดผลลัพธ์ก่อนหน้านี้ก่อนที่จะเริ่มแบบสอบถามใหม่ อย่างน้อยนั่นเป็นวิธีที่ DBMS ที่ฉันใช้ทำงานอยู่
Raúl Salinas-Monteagudo

ทำไมคุณไม่ทำอะไรแบบนี้ ลอง (เปิดการเชื่อมต่อ) {ลอง (หลายชุดคำสั่ง & resultsets) {โดยเฉพาะอย่างยิ่งเมื่อแบบสอบถามผลลัพธ์ถัดไปสามารถคำนวณกับรายการก่อนหน้าได้
Daniel Hajduk

Daniel: เมื่อฉันใช้รูปแบบนั้นแบ็กเอนด์ JDBC พื้นฐานไม่สนับสนุนการเปิด ResultSet และเปิดอันที่สอง
Raúl Salinas-Monteagudo

rascio, คุณสามารถทำอะไรก็ได้ที่คุณต้องการในบล็อก catch
Raúl Salinas-Monteagudo

73

จากjavadocs :

เมื่อStatementวัตถุถูกปิดResultSetวัตถุปัจจุบันของวัตถุนั้นก็จะถูกปิดเช่นกัน

แต่ javadocs จะไม่ได้รับความชัดเจนมากกับว่าStatementและจะปิดเมื่อคุณปิดพื้นฐานResultSet Connectionพวกเขาเพียงแค่ระบุว่าการปิดการเชื่อมต่อ:

เผยแพร่Connectionฐานข้อมูลของวัตถุนี้และทรัพยากร JDBC ทันทีแทนที่จะรอให้ปล่อยโดยอัตโนมัติ

ในความคิดของฉันเสมออย่างชัดเจนใกล้ResultSets, StatementsและConnectionsเมื่อเสร็จสิ้นการกับพวกเขาเช่นการดำเนินการcloseอาจแตกต่างกันระหว่างคนขับฐานข้อมูล

คุณสามารถบันทึกรหัสจานหม้อไอน้ำจำนวนมากด้วยตัวเองโดยใช้วิธีเช่นcloseQuietlyในDBUtilsจาก Apache


1
ขอบคุณ dogbane ประเด็นคือคุณไม่สามารถพึ่งพาการใช้งานของ Connection.close ใช่ไหม?
Zeemee

1
ข้อความข้างเคียงสำหรับ n00bs อย่างฉัน - stackoverflow.com/questions/3992199/what-is-boilerplate-code
david blaine

39

ตอนนี้ฉันใช้ Oracle กับ Java ที่นี่มุมมองของฉัน:

คุณควรปิดResultSetและStatementชัดเจนเพราะ Oracle มีปัญหาก่อนหน้านี้กับการเปิดเคอร์เซอร์ไว้แม้ว่าจะปิดการเชื่อมต่อแล้วก็ตาม ถ้าคุณไม่ปิดResultSet(เคอร์เซอร์) ก็จะโยนความผิดพลาดเช่นเคอร์เซอร์ที่เปิดเกินขีด จำกัด สูงสุด

ฉันคิดว่าคุณอาจประสบปัญหาเดียวกันกับฐานข้อมูลอื่นที่คุณใช้

นี่คือการสอนปิด ResultSet เมื่อเสร็จแล้ว :

ปิด ResultSet เมื่อเสร็จสิ้น

ปิดResultSetวัตถุทันทีที่คุณทำงานกับResultSetวัตถุให้เสร็จแม้ว่าStatementวัตถุจะปิด ResultSetวัตถุโดยปริยายเมื่อปิดก็ตามการปิดResultSetอย่างชัดแจ้งจะทำให้มีโอกาสที่ตัวรวบรวมข้อมูลขยะจะจำหน่วยความจำได้เร็วที่สุดเนื่องจากResultSetวัตถุอาจครอบครองหน่วยความจำมากขึ้นอยู่กับแบบสอบถาม

ResultSet.close();


ขอบคุณ hilal นี่เป็นเหตุผลที่ดีที่ควรปิดมันให้เร็วที่สุด อย่างไรก็ตามมันจะสำคัญหรือไม่ถ้า ResultSet และ Statement ปิดอย่างรุนแรงก่อนการเชื่อมต่อ (ซึ่งหมายความว่าในบางกรณี: ไม่เร็วที่สุด)
Zeemee

หากคุณปิดการเชื่อมต่อมันจะปิดคำสั่ง resultset ทั้งหมดและคุณควรปิด resultset ก่อนการเชื่อมต่อ

และทำไมฉันควรปิด resultset ก่อนการเชื่อมต่อ? คุณหมายถึงเพราะปัญหาไดรเวอร์ oracle?
Zeemee

1
นี่คือคำชี้แจงทั่วไปเพิ่มเติม :) stackoverflow.com/questions/103938/ …

ในทางทฤษฎีถ้าคุณปิดคำสั่งคุณไม่จำเป็นต้องปิดชุดผลลัพธ์ แต่อาจเป็นแนวปฏิบัติที่ดี
rogerdpack

8

หากคุณต้องการรหัสขนาดกะทัดรัดมากขึ้นผมขอแนะนำให้ใช้DbUtils Apache คอมมอนส์ ในกรณีนี้:

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
จะเกิดอะไรขึ้นถ้าฉันใช้รหัสนี้แทน rs.close (), stmt.close (), conn.close ()
Onkar Musale

3

วิธีที่ถูกต้องและปลอดภัยสำหรับการปิดทรัพยากรที่เกี่ยวข้องกับ JDBC นี้ (นำมาจากวิธีการปิดทรัพยากร JDBC อย่างเหมาะสม - ทุกครั้ง ):

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

ไม่สำคัญว่าConnectionจะเป็นพูลหรือไม่ แม้แต่การเชื่อมต่อ poolable ก็ต้องทำความสะอาดก่อนกลับไปที่สระ

โดยทั่วไป "ล้างข้อมูล" หมายถึงการปิดชุดผลลัพธ์และย้อนกลับธุรกรรมที่ค้างอยู่ แต่ไม่ปิดการเชื่อมต่อ มิฉะนั้นการรวมกำไรจะหมดไป


2

ไม่คุณไม่จำเป็นต้องปิดอะไร แต่เชื่อมต่อ ตามข้อกำหนด JDBC การปิดวัตถุที่สูงกว่าใด ๆ จะปิดวัตถุที่ต่ำกว่าโดยอัตโนมัติ การปิดConnectionจะปิดStatementการเชื่อมต่อใด ๆที่สร้างขึ้น ปิดใด ๆStatementจะปิดทั้งหมดของที่ถูกสร้างขึ้นโดยที่ResultSet Statementไม่สำคัญว่าConnectionจะเป็นพูลหรือไม่ แม้แต่การเชื่อมต่อ poolable ก็ต้องทำความสะอาดก่อนกลับไปที่สระ

แน่นอนว่าคุณอาจมีลูปซ้อนกันเป็นเวลานานในการConnectionสร้างคำสั่งมากมาย ฉันแทบไม่เคยปิดResultSetเลยดูเหมือนว่ามากเกินไปเมื่อปิดStatementหรือConnectionจะปิดมัน


1

ฉันสร้างวิธีการต่อไปนี้เพื่อสร้าง One Liner ที่สามารถใช้ซ้ำได้:

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

ฉันใช้รหัสนี้ในคลาสผู้ปกครองที่สืบทอดไปยังคลาสของฉันทั้งหมดที่ส่งแบบสอบถาม DB ฉันสามารถใช้ Oneliner กับการค้นหาทั้งหมดแม้ว่าฉันจะไม่มีผลลัพท์ของชุดก็ตาม แต่วิธีการก็คือการปิด ResultSet, Statement, Connection ในลำดับที่ถูกต้อง นี่คือสิ่งที่ในที่สุดบล็อกของฉันดูเหมือน

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

-1

เท่าที่ฉันจำได้ใน JDBC ปัจจุบัน Resultsets และ statement ใช้อินเตอร์เฟส AutoCloseable ซึ่งหมายความว่าพวกเขาจะปิดโดยอัตโนมัติเมื่อถูกทำลายหรือออกนอกขอบเขต


3
ไม่นั่นหมายความว่าcloseมีการเรียกใช้เมื่อสิ้นสุดคำสั่ง try-with-resources ดูdocs.oracle.com/javase/tutorial/essential/exceptions/...และdocs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html
Zeemee

-1

ฟังก์ชั่นอำนวยความสะดวกบางอย่าง:

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

ไม่มีอะไรที่นี่ที่ปิดอะไร แค่วงวนที่ไม่มีจุดหมายที่อ่านคำตอบทั้งหมดอย่างสิ้นเปลืองแม้ว่ามันจะไม่ต้องการอีกต่อไปก็ตาม
มาร์ควิสแห่ง Lorne

-1

ด้วยรูปแบบ Java 6 ฉันคิดว่าดีกว่าที่จะตรวจสอบว่าปิดหรือไม่ก่อนปิด (ตัวอย่างเช่นหากการเชื่อมต่อบางกลุ่มขับการเชื่อมต่อในเธรดอื่น) - ตัวอย่างเช่นปัญหาเครือข่ายบางอย่าง - คำสั่งและสถานะชุดผลลัพธ์สามารถปิด (มักจะไม่เกิดขึ้น แต่ฉันมีปัญหากับ Oracle และ DBCP) รูปแบบของฉันสำหรับนั้น (ในไวยากรณ์ Java เก่ากว่า) คือ:

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

ในทางทฤษฎีมันไม่ได้สมบูรณ์แบบ 100% เพราะระหว่างการตรวจสอบสถานะใกล้และปิดตัวเองมีห้องเล็ก ๆ สำหรับการเปลี่ยนแปลงของรัฐ ในกรณีที่เลวร้ายที่สุดคุณจะได้รับคำเตือนเป็นเวลานาน - แต่มันมีค่าน้อยกว่าความเป็นไปได้ของการเปลี่ยนสถานะในเคียวรีการรันระยะยาว เราใช้รูปแบบนี้ในการผลิตด้วยการโหลด "avarage" (ผู้ใช้ 150 คนพร้อมกัน) และเราไม่มีปัญหากับมัน - ดังนั้นอย่าเคยเห็นข้อความเตือนนั้น


คุณไม่จำเป็นต้องทำการisClosed()ทดสอบเพราะการปิดสิ่งเหล่านี้ที่ปิดไปแล้วนั้นเป็นแบบไม่ต้องเลือก ซึ่งช่วยขจัดปัญหาเรื่องระยะเวลาของหน้าต่าง ซึ่งจะถูกกำจัดด้วยการสร้างConnection, StatementและResultSetตัวแปรท้องถิ่น
มาร์ควิสแห่ง Lorne
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.