0Pricing
Java Academy · درس

PreparedStatement

استعلامات آمنة معلَّمة بالمعلمات

PreparedStatement درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.

لماذا PreparedStatement؟

إنشاء SQL بضم إدخال المستخدم إلى النص البرمجي أمر خطير وبطيء.

تحل PreparedStatement المشكلتين معاً:

  • الأمان — تُرسل القيم منفصلة عن SQL، مما يمنع حقن SQL.
  • الأداء — يمكن لقاعدة البيانات تحليل الاستعلام وتخطيطه مرة واحدة، ثم إعادة استخدامه.

مشكلة حقن SQL

تأملوا إنشاء استعلام باستخدام ضم السلاسل النصية. إذا كتب مستخدم '; DROP TABLE users; -- كاسمه، فستنفّذ SQL الناتجة الأمر المضمّن فيه.

هذا هو حقن SQL، وهو من أقدم ثغرات الويب وأكثرها إضراراً. وتجعل 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.

لا تلامس القيمة نص SQL، لذلك لا يمكن أن يحدث حقن SQL مهما كان ما يكتبه المستخدم.

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 الخاصة به، وينبغي إغلاقه عند الانتهاء منه.

داخل try-with-resources، صرّحوا عن العبارة بحيث تُغلَق قبل الاتصال. وإذا أعدتم استخدام العبارة مرات عديدة داخل حلقة، فأعدّوها مرة واحدة خارج الحلقة، وأعيدوا ربط المعاملات داخلها فقط؛ إذ إن إعدادها من جديد في كل تكرار يهدر خطة التنفيذ المخزنة مؤقتاً.

تحقق سريع

لماذا تمنع PreparedStatement هجوم حقن SQL؟

مراجعة

لقد تعلّمتم كتابة استعلامات آمنة وسريعة:

  • استخدموا العناصر النائبة ? ودوال التعيين المعيّنة النوع (بفهرس يبدأ من 1).
  • اربطوا القيم باستخدام setString وsetInt وsetNull وما إلى ذلك.
  • استرجعوا المفاتيح المُنشأة تلقائيًا باستخدام RETURN_GENERATED_KEYS وgetGeneratedKeys.
  • نفّذوا عمليات كتابة كثيرة على دفعات باستخدام addBatch + executeBatch.
  • لا تُسلسلوا مُدخلات المستخدم في SQL مطلقًا.

الأسئلة الشائعة

هل درس «PreparedStatement» مجاني؟

نعم — نص درس «PreparedStatement» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.

ماذا ستتعلم في «PreparedStatement»؟

استعلامات آمنة معلَّمة بالمعلمات تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟

لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «PreparedStatement»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟

نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أساسيات JDBC
  2. PreparedStatement
  3. المعاملات
  4. تجميع الاتصالات باستخدام HikariCP
← العودة إلى Java Academy