0Pricing
Java Academy · บทเรียน

PreparedStatement

คำค้นแบบกำหนดพารามิเตอร์อย่างปลอดภัย

PreparedStatement เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดจึงควรใช้ PreparedStatement

การสร้างเอสคิวแอลด้วยการต่อข้อความจากข้อมูลที่ผู้ใช้ป้อนนั้นไม่ปลอดภัยและทำงานช้า

PreparedStatementแก้ปัญหาทั้งสองประการได้

  • ความปลอดภัย — ค่าต่าง ๆ จะถูกส่งแยกจากเอสคิวแอล จึงป้องกันการฉีดคำสั่งเอสคิวแอล
  • ประสิทธิภาพ — ฐานข้อมูลสามารถแยกวิเคราะห์และวางแผนคำค้นเพียงครั้งเดียว แล้วนำกลับมาใช้ซ้ำได้

ปัญหาการฉีดคำสั่งเอสคิวแอล

ลองพิจารณาการสร้างคำค้นด้วยการต่อข้อความ หากผู้ใช้พิมพ์ '; DROP TABLE users; -- เป็นชื่อของตนเอง เอสคิวแอลที่ต่อขึ้นจะดำเนินการดังกล่าวเป็นคำสั่ง

นี่คือ การฉีดคำสั่งเอสคิวแอล ซึ่งเป็นช่องโหว่บนเว็บที่เก่าแก่และสร้างความเสียหายมากที่สุดประเภทหนึ่ง PreparedStatementทำให้ปัญหานี้เกิดขึ้นไม่ได้ตั้งแต่การออกแบบ

public class Main {
    public static void main(String[] args) {
        String userInput = "'; DROP TABLE users; --";
        // Dangerous: never do this
        String unsafe = "SELECT * FROM users WHERE name = '" + userInput + "'";
        System.out.println("Injected SQL becomes:");
        System.out.println(unsafe);
    }
}

ตัวแทนค่า

ใน PreparedStatement ให้เขียน ? แทนค่าแต่ละค่าที่คุณจะส่งให้ภายหลัง

จากนั้นกำหนดค่าด้วยเมธอดตั้งค่าตามชนิดข้อมูล

  • ps.setString(1, name)
  • ps.setInt(2, age)

ดัชนีพารามิเตอร์จะเริ่มนับจาก 1 ตามลำดับของเครื่องหมาย ?

คำค้นที่ปลอดภัย

สร้างคำสั่งด้วย prepareStatement กำหนดพารามิเตอร์ แล้วเรียกใช้ executeQuery

ค่าจะไม่ถูกนำไปปะปนกับข้อความเอสคิวแอล ดังนั้นการฉีดคำสั่งจึงเกิดขึ้นไม่ได้ ไม่ว่าผู้ใช้จะพิมพ์อะไร

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "SELECT id FROM users WHERE name = ?";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Ada");
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) System.out.println(rs.getInt("id"));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

การกำหนดค่าหลายพารามิเตอร์

เครื่องหมาย ? แต่ละตัวต้องมีการเรียกใช้เมธอดตั้งค่าของตัวเอง ลำดับมีความสำคัญ พารามิเตอร์ที่ 1 คือเครื่องหมาย ? ตัวแรก พารามิเตอร์ที่ 2 คือตัวที่สอง และเป็นเช่นนี้ต่อไป

ใช้เมธอดตั้งค่าให้ตรงกับชนิดข้อมูลของคอลัมน์ เช่น setInt, setString, setDouble, setBoolean และอื่น ๆ

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name, age, active) VALUES (?, ?, ?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, "Grace");
            ps.setInt(2, 42);
            ps.setBoolean(3, true);
            int rows = ps.executeUpdate();
            System.out.println("Inserted " + rows + " row");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

การจัดการ NULL

หากต้องการกำหนดค่า SQL NULL ให้ใช้ setNull(index, type) พร้อมค่าคงที่จาก java.sql.Types

อย่าส่งค่า Java null ให้ setString แล้วหวังว่าจะทำงานได้ ให้ระบุอย่างชัดเจน

  • ps.setNull(2, java.sql.Types.INTEGER)

การรับคีย์ที่สร้างขึ้นอัตโนมัติ

หลังจาก INSERT คุณมักต้องใช้รหัสที่สร้างขึ้นอัตโนมัติ

ส่ง Statement.RETURN_GENERATED_KEYS ขณะเตรียมคำสั่ง แล้วอ่านค่าด้วย ps.getGeneratedKeys()

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO users(name) VALUES (?)";
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
            ps.setString(1, "Linus");
            ps.executeUpdate();
            try (ResultSet keys = ps.getGeneratedKeys()) {
                if (keys.next()) System.out.println("New id: " + keys.getLong(1));
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

การแทรกข้อมูลเป็นชุด

การแทรกข้อมูลหลายพันแถวด้วยคำสั่งทีละรายการทำได้ช้า ให้ใช้การประมวลผลเป็นชุด

  • กำหนดพารามิเตอร์ แล้วเรียกใช้ ps.addBatch()
  • ทำซ้ำกับแต่ละแถว
  • เรียกใช้ ps.executeBatch() เพียงครั้งเดียว

วิธีนี้ส่งแถวทั้งหมดไปพร้อมกัน จึงลดการติดต่อไปกลับได้อย่างมาก

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/shop";
        String sql = "INSERT INTO tags(label) VALUES (?)";
        String[] labels = {"java", "jdbc", "sql"};
        try (Connection conn = DriverManager.getConnection(url, "app", "secret");
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (String label : labels) {
                ps.setString(1, label);
                ps.addBatch();
            }
            int[] counts = ps.executeBatch();
            System.out.println("Batched " + counts.length + " inserts");
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}

การนำคำสั่งกลับมาใช้ซ้ำ

คุณสามารถเรียกใช้ PreparedStatement เดียวหลายครั้งด้วยพารามิเตอร์ที่แตกต่างกัน

กำหนดค่าตัวแทนอีกครั้ง แล้วเรียกใช้ execute ซ้ำ ฐานข้อมูลจะนำแผนการดำเนินการที่แคชไว้ม​​าใช้ซ้ำ จึงเป็นเหตุผลว่าทำไมคำสั่งที่เตรียมไว้จึงทำงานเร็วกว่าเมื่อใช้กับคำค้นซ้ำ ๆ

ห้ามต่อข้อความ

กฎสำคัญที่สุดคือ ค่าทุกค่าที่เปลี่ยนแปลงได้ต้องเป็นพารามิเตอร์ ?

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

ขอบเขตการปิดและการใช้ซ้ำ

PreparedStatementผูกอยู่กับ Connection ของตนเอง และควรปิดเมื่อใช้งานเสร็จ

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

ตรวจสอบอย่างรวดเร็ว

เหตุใด PreparedStatement จึงป้องกันการแทรกคำสั่งฐานข้อมูลได้

สรุปทบทวน

คุณได้เรียนรู้การเขียนคำสั่งฐานข้อมูลที่ปลอดภัยและรวดเร็ว:

  • ใช้ตัวแทนตำแหน่ง ? และตัวตั้งค่าตามชนิดข้อมูล (ดัชนีเริ่มต้นที่ 1)
  • ผูกค่าด้วย setString, setInt, setNull เป็นต้น
  • ดึงคีย์ที่สร้างอัตโนมัติด้วย RETURN_GENERATED_KEYS และ getGeneratedKeys
  • รวมการเขียนข้อมูลจำนวนมากเป็นชุดด้วย addBatch + executeBatch
  • ห้ามนำข้อมูลที่ผู้ใช้ป้อนมาต่อเข้ากับคำสั่งฐานข้อมูล

คำถามที่พบบ่อย

บทเรียน “PreparedStatement” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “PreparedStatement” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “PreparedStatement”

คำค้นแบบกำหนดพารามิเตอร์อย่างปลอดภัย คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “PreparedStatement” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม

ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐาน JDBC
  2. PreparedStatement
  3. ธุรกรรม
  4. การรวมการเชื่อมต่อด้วย HikariCP
← กลับไปที่ Java Academy