การใช้ PreparedStatement ซ้ำหลาย ๆ ครั้ง


98

ในกรณีของการใช้ 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

คำตอบ:


146

วิธีที่สองนั้นมีประสิทธิภาพมากขึ้นเล็กน้อย แต่วิธีที่ดีกว่ามากคือการดำเนินการเป็นกลุ่ม:

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 ว่าคุณสามารถดำเนินการได้กี่ชุดพร้อมกัน ตัวอย่างเช่นคุณอาจต้องการดำเนินการทุก 1,000 แบทช์:

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

inside the same method block- คุณหมายความว่าทุกเธรดจะมีสแต็กของตัวเองและการเชื่อมต่อและคำสั่งเหล่านี้อยู่ในสแต็กจากด้านหนึ่งและจากแหล่งข้อมูลอื่นจะให้การเรียกใช้ executeFunction ใหม่ทุกครั้ง (== ทุกเธรด) แยกอินสแตนซ์ของการเชื่อมต่อ ฉันเข้าใจคุณใช่ไหม "
Pavel_K

ฉันกำลังทำวิธีแรก แต่ในขณะที่ตรวจสอบใน SQL profiler ฉันเห็นคำสั่งที่เตรียมไว้ซ้ำ ๆ มากกว่าหนึ่ง ฉันคิดไม่ออกว่าเหตุใดจึงแสดงข้อความหลายรายการ ต้องการความช่วยเหลือ
จอมโกง

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

13

การวนซ้ำในโค้ดของคุณเป็นเพียงตัวอย่างที่ง่ายเกินไปใช่ไหม

มันจะดีกว่าถ้าสร้างPreparedStatementเพียงครั้งเดียวและใช้ซ้ำซ้ำแล้วซ้ำอีกในลูป

ในสถานการณ์ที่เป็นไปไม่ได้ (เพราะมันซับซ้อนในการไหลของโปรแกรมมากเกินไป) ยังคงเป็นประโยชน์ที่จะใช้ a PreparedStatementแม้ว่าคุณจะใช้เพียงครั้งเดียวก็ตามเนื่องจากงานฝั่งเซิร์ฟเวอร์ (แยกวิเคราะห์ SQL และแคชการดำเนินการ แผน) จะยังคงลดลง

เพื่อจัดการกับสถานการณ์ที่คุณต้องการใช้ฝั่ง Java ซ้ำPreparedStatementไดรเวอร์ JDBC บางตัว (เช่น Oracle) มีคุณสมบัติการแคช: หากคุณสร้างPreparedStatementSQL เดียวกันบนการเชื่อมต่อเดียวกันมันจะทำให้คุณเหมือนเดิม (แคช ) ตัวอย่าง.

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


1
ในความเป็นจริงการเชื่อมต่อมีเธรดพิเศษและทุกคำสั่งจะดำเนินการในนั้น แต่ฉันเข้าถึงผ่านสแต็กของคำสั่งที่เตรียมไว้ไปยังเธรดนั้น ดังนั้นหัวข้ออื่น ๆ พร้อมกันครั้งแรกผ่านเพียง params จำเป็นในการสร้างงบเตรียมทั้งหมด แต่แล้วพวกเขาก็สามารถปรับเปลี่ยนพารามิเตอร์พร้อมกัน
เหล็ก Plume
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.