JDBC وقواعد البيانات

مقدمة إلى JDBC

15 دقيقة الدرس 1 من 13

مقدمة إلى JDBC

JDBC — اختصار Java Database Connectivity — هي الواجهة البرمجية القياسية في Java للاتصال بقواعد البيانات العلائقية والاستعلام منها وتحديثها. تأتي مضمّنةً في JDK (في حزمتَي java.sql وjavax.sql)، لذا لا تحتاج إلى أي تبعية خارجية لمجرد فتح اتصال. كل شيء من JPA وHibernate إلى Spring Data وjOOQ يستدعي JDBC في نهاية المطاف تحت الغطاء.

يفترض هذا الدرس أنك مرتاح لمفاهيم Java مثل الأنواع العامة (Generics)، والتعبيرات اللامبدية (Lambdas)، والتدفقات (Streams)، وإدارة الموارد عبر try-with-resources. لن نعيد شرح هذه المفاهيم؛ بل سنستخدمها كأدوات جاهزة.

لماذا لا تزال JDBC مهمة

قد تتساءل لماذا تتعلم واجهة برمجية من عام 1997 بينما توجد أُطر عمل أعلى مستوى. السبب في أمرين:

  • التشخيص والضبط. كل استثناء تُلقيه أُطر ORM ينتهي في نهاية مكدس الاستدعاء عند JDBC. فهم هذه الواجهة يعني قدرتك على تشخيص الاستعلامات البطيئة، واستنزاف حوض الاتصالات، وأخطاء حدود المعاملات التي تبدو غامضة على مستوى الإطار.
  • التحكم الكامل. الإدراج الدُفعي الضخم، واستدعاء الإجراءات المخزّنة، وتعيينات الأنواع المخصصة، والميزات الخاصة بكل بائع — كل ذلك يكون في JDBC الخام أكثر وضوحًا من المصارعة مع تعليقات ORM.
JDBC طبقة تجريد وليست بروتوكولًا. هي لا تحدّد كيف تتحدث إلى قاعدة البيانات — بل تحدد واجهة Java محمولة. البروتوكول الفعلي للشبكة تتولاه مشغّلة (Driver) يكتبها بائع قاعدة البيانات أو المجتمع. شفرتك تستدعي JDBC؛ والمشغّلة تستدعي قاعدة البيانات.

نموذج الطبقات الأربع

فهم JDBC يستلزم فهم بنيتها ذات الطبقات:

  1. شفرة تطبيقك — تستدعي واجهات java.sql القياسية مثل Connection وStatement وResultSet.
  2. واجهة JDBC البرمجية — الواجهات والفئات المجردة التي تحدد العقد (معرّفة في java.sql وjavax.sql).
  3. DriverManager / DataSource — الجسر الذي يحمّل المشغّلة الصحيحة ويمنحك كائن Connection.
  4. مشغّلة JDBC — ملف JAR من البائع ينفّذ واجهات JDBC ويتحدث بروتوكول قاعدة البيانات الفعلي (مثل بروتوكول MySQL السلكي، أو بروتوكول PostgreSQL الثنائي).

لأن شفرتك لا تلمس إلا الواجهات المعرَّفة في الطبقة الثانية، فإن التبديل من PostgreSQL إلى MySQL — من الناحية النظرية — لا يتطلب سوى استبدال المشغّلة وتغيير سلسلة الاتصال دون أي تعديل على منطق التطبيق.

أنواع المشغّلات

عرّفت مواصفة JDBC أصلًا أربعة أنواع من المشغّلات. في الواقع العملي لن تصادف إلا النوع الرابع اليوم:

  • النوع الأول — جسر JDBC-ODBC (أُزيل في Java 8): ترجم استدعاءات JDBC إلى ODBC. مهجور منذ زمن.
  • النوع الثاني — مشغّلة الواجهة الأصلية: تُغلّف مكتبة C الأصلية لقاعدة البيانات. تستلزم وجود شفرة أصلية على الجهاز.
  • النوع الثالث — مشغّلة بروتوكول الشبكة: ترسل الاستدعاءات إلى خادم وسيط. نادر الاستخدام.
  • النوع الرابع — مشغّلة Java خالصة: ملف JAR مكتوب بالكامل بـ Java يتحدث بروتوكول قاعدة البيانات السلكي مباشرة. هذا ما هو عليه mysql-connector-j وpostgresql وh2 وكل مشغّلة حديثة. لا تبعيات أصلية؛ أضف ملف JAR فحسب.
استخدم دائمًا مشغّلة من النوع الرابع. أضفها عبر Maven أو Gradle وانتهيت. لـ PostgreSQL هي org.postgresql:postgresql؛ لـ MySQL هي com.mysql.cj:mysql-connector-j؛ ولـ H2 (ذاكرة فقط، ممتازة للاختبارات) هي com.h2database:h2.

الواجهات الرئيسية في JDBC

تُبنى الواجهة البرمجية بأكملها حول عدد محدود من الواجهات التي ستستخدمها في كل برنامج JDBC:

  • java.sql.Connection — تمثّل اتصالًا فيزيائيًا واحدًا بقاعدة البيانات. تملك المعاملات. يجب إغلاقها دائمًا في كتلة finally أو try-with-resources.
  • java.sql.Statement — تنفّذ سلسلة SQL ثابتة. لا تستخدمها أبدًا مع بيانات يدخلها المستخدم (خطر حقن SQL).
  • java.sql.PreparedStatement — تُجمّع مسبقًا سلسلة SQL ذات معاملات. الاختيار الصحيح لمعظم الاستعلامات.
  • java.sql.CallableStatement — تستدعي الإجراءات المخزّنة.
  • java.sql.ResultSet — مؤشر يتنقل على الصفوف التي يعيدها الاستعلام. يتم الوصول إلى الصفوف واحدًا تلو الآخر بـ next().
  • javax.sql.DataSource — مصنع لكائنات Connection؛ البديل الإنتاجي لـ DriverManager. أحواض الاتصالات تنفّذ هذه الواجهة.

مثال شامل مصغّر

قبل الخوض في كل جزء بالتفصيل في الدروس التالية، إليك أصغر برنامج JDBC ممكن لترى كيف تتلاءم القطع معًا. يستخدم H2 في وضع الذاكرة — لا تحتاج قاعدة بيانات خارجية:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JdbcHello { public static void main(String[] args) throws Exception { // 1. الحصول على اتصال — DriverManager يعثر على مشغّلة H2 // تلقائيًا لأن ملف JAR الخاص بها موجود في classpath. String url = "jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1"; try (Connection conn = DriverManager.getConnection(url, "sa", "")) { // 2. إنشاء Statement وتنفيذ DDL try (Statement stmt = conn.createStatement()) { stmt.execute("CREATE TABLE greeting (message VARCHAR(100))"); stmt.execute("INSERT INTO greeting VALUES ('Hello from JDBC')"); } // 3. استعلام وقراءة ResultSet try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT message FROM greeting")) { while (rs.next()) { System.out.println(rs.getString("message")); } } } // يُغلق conn تلقائيًا هنا } }

المخرج: Hello from JDBC

لاحظ كتل try-with-resources المتداخلة. كلٌّ من Connection وStatement وResultSet تنفّذ AutoCloseable، لذا يُغلقها JVM بترتيب عكسي عند خروج كل كتلة — حتى عند وقوع استثناءات. نسيان إغلاق هذه الكائنات هو أكثر تسرّبات موارد JDBC شيوعًا.

لا تستخدم Statement مطلقًا مع مدخلات المستخدم. المثال أعلاه يستخدم سلسلة مُشفَّرة ثابتة وهو آمن. بمجرد أن تظهر قيمة من المستخدم في سلسلة SQL مُتسلسَلة تصبح عرضة لحقن SQL. يغطي درس PreparedStatement الأسلوب الصحيح — لكن ضع هذه القاعدة في ذهنك منذ الآن.

كيف تُحمَّل المشغّلة

منذ Java 6 لم تعد تحتاج إلى استدعاء Class.forName("org.postgresql.Driver") يدويًا. يستخدم JDK آلية ServiceLoader: كل ملف JAR لمشغّلة JDBC يُعلن عن نفسه في META-INF/services/java.sql.Driver. عند استدعاء DriverManager.getConnection(url, ...)، يتكرر على المشغّلات المسجّلة ويعرض على كل منها الرابط، والأولى التي تتعرف على بادئة الرابط (مثل jdbc:postgresql:) تقبل الاستدعاء وتعيد Connection.

// الأسلوب القديم (عهد Java 5) — لم يعد مطلوبًا: // Class.forName("org.postgresql.Driver"); // الأسلوب الحديث — استدعِ getConnection فقط؛ ServiceLoader يتولى الاكتشاف: Connection conn = DriverManager.getConnection( "jdbc:postgresql://localhost:5432/mydb", "alice", "secret");

تركيب عنوان URL في JDBC

كل عنوان JDBC URL يتبع النمط jdbc:<subprotocol>:<subname>. الاسم الفرعي خاص بكل بائع. أمثلة:

  • jdbc:h2:mem:testdb — قاعدة بيانات H2 في الذاكرة باسم "testdb"
  • jdbc:postgresql://localhost:5432/mydb — PostgreSQL على الجهاز المحلي
  • jdbc:mysql://localhost:3306/mydb?useSSL=false — MySQL مع معامل استعلام
  • jdbc:sqlserver://host:1433;databaseName=mydb — SQL Server

DriverManager مقابل DataSource

يفتح DriverManager.getConnection() اتصالًا TCP فيزيائيًا جديدًا في كل مرة يُستدعى. في تطبيق حقيقي هذا مكلف للغاية (كل فتح/إغلاق يستغرق ~10-100 مللي ثانية ويستهلك واصفة ملف). الحل هو حوض الاتصالات — تنفيذ لـ DataSource يحتفظ بمجموعة من الاتصالات المفتوحة مسبقًا ويوزّعها عند الطلب، ويعيدها إلى الحوض عند close().

المكتبات الشائعة لأحواض الاتصالات — HikariCP وc3p0 وDBCP — كلها تنفّذ javax.sql.DataSource، لذا تعتمد شفرة DAO الخاصة بك على الواجهة فحسب. سنفحص الأحواض بالتفصيل لاحقًا في هذا الدرس.

قاعدة الإبهام للشفرة الإنتاجية: استخدم DataSource (حوض اتصالات) في كل مكان. استخدم DriverManager فقط في سكربتات مستقلة سريعة أو اختبارات أو أمثلة تعليمية كهذا الدرس.

الخلاصة

JDBC هي واجهة Java المحايدة للبائعين للوصول إلى قواعد البيانات العلائقية. يعني نموذجها ذو الطبقات — شفرتك تستدعي الواجهات، والمشغّلة تنفّذها — أنك تستطيع تبديل قواعد البيانات بتغييرات طفيفة في الشفرة. الكائنات الخمسة الأساسية التي ستتعامل معها هي Connection وStatement وPreparedStatement وResultSet وDataSource. تُحمَّل المشغّلة تلقائيًا عبر ServiceLoader؛ لا حاجة لتسجيل يدوي. في الإنتاج استخدم دائمًا DataSource من حوض اتصالات؛ DriverManager للسكربتات البسيطة والاختبارات. الدرس التالي يتعمق في كيفية ضبط الاتصال والحصول عليه بصورة صحيحة.