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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- พื้นฐาน JDBC
- PreparedStatement
- ธุรกรรม
- การรวมการเชื่อมต่อด้วย HikariCP