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

الاتصال بقاعدة البيانات

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

الاتصال بقاعدة البيانات

قبل أن تتمكّن من تنفيذ جملة SQL واحدة تحتاج إلى كائن Connection حي. تمنحك JDBC آليتين مختلفتين للحصول عليه: الفئة منخفضة المستوى DriverManager والواجهة رفيعة المستوى DataSource. إنّ فهم الاثنتين — ومعرفة متى تستخدم كلًّا منهما — هو أساس كل ما يأتي في هذا البرنامج التعليمي.

كيف تحمّل JDBC برنامج التشغيل

برنامج تشغيل JDBC ما هو إلا ملف JAR في مسار الفئات يحتوي على فئة تنفّذ الواجهة java.sql.Driver. منذ JDBC 4.0 (Java 6)، يُسجّل برنامج التشغيل نفسَه تلقائيًا عبر آلية مزوّد الخدمة (SPI): يتضمّن ملف JAR ملفًا باسم META-INF/services/java.sql.Driver يسرد فئة التشغيل، ويقرأه DriverManager عند بدء التشغيل. لم تعد بحاجة إلى كتابة Class.forName("com.mysql.cj.jdbc.Driver") — وإن كنت قد تراه في الكود القديم.

ما يعنيه SPI عمليًا: أضف ملف JAR الخاص بالمشغّل (مثل mysql-connector-j عبر Maven/Gradle) إلى مشروعك وسيُكتشف تلقائيًا. لا حاجة لأي كود تسجيل يدوي.

DriverManager — الأسلوب البسيط

يُعدّ DriverManager.getConnection(url, user, password) أكثر طريقة مباشرة للحصول على اتصال. فهو يتكرّر على المشغّلات المسجّلة ويسأل كلًّا منها ما إذا كان يفهم عنوان URL الذي أمدّه به.

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class SimpleConnect { public static void main(String[] args) throws SQLException { String url = "jdbc:mysql://localhost:3306/shop?useSSL=false&serverTimezone=UTC"; String user = "appuser"; String pass = "secret"; try (Connection conn = DriverManager.getConnection(url, user, pass)) { System.out.println("Connected: " + conn.getMetaData().getDatabaseProductName()); } // conn.close() تُستدعى تلقائيًا بواسطة try-with-resources } }

يضمن حقل try-with-resources إغلاق الاتصال حتى في حالة رمي استثناء. لا تُهمل هذا أبدًا — فالاتصال غير المغلق يُسرّب مقبسًا (socket) وجلسةً على جانب الخادم.

تشريح عنوان URL الخاص بـ JDBC

يتبع كل عنوان JDBC النمط jdbc:<subprotocol>:<subname>. يُحدّد البروتوكول الفرعي المشغّل؛ أما الاسم الفرعي فيعتمد على المشغّل. أمثلة شائعة:

  • MySQL / MariaDB: jdbc:mysql://host:3306/dbname?param=value
  • PostgreSQL: jdbc:postgresql://host:5432/dbname
  • H2 (في الذاكرة، مثالية للاختبارات): jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
  • SQLite: jdbc:sqlite:/path/to/file.db

تختلف معاملات سلسلة الاستعلام بحسب المشغّل لكنها تشمل في الغالب:

  • useSSL=true / sslmode=require — تشفير الاتصال (استخدمه دائمًا في الإنتاج).
  • serverTimezone=UTC (MySQL) — يمنع أخطاء التفاوض على المنطقة الزمنية.
  • connectTimeout=5000 — الحد الأقصى بالمللي ثانية للانتظار حتى اكتمال المصافحة TCP.
لا تضمّن بيانات الاعتماد مباشرةً في كود المصدر أبدًا. حمّلها من متغيرات البيئة أو من مدير أسرار. فعنوان URL يحتوي على كلمة مرور تنتهي في نظام التحكم في الإصدارات هو حادثة أمنية في انتظار الوقوع.

لماذا لا يكفي DriverManager في التطبيقات الحقيقية

فتح اتصال TCP جديد لكل استدعاء قاعدة بيانات عملية مكلفة — تستغرق عادةً من 20 إلى 100 مللي ثانية على شبكة محلية. تحت أي حمل يُعتدّ به، سيُنهك تطبيق ويب يستخدم DriverManager مباشرةً إما حدود اتصال قاعدة البيانات أو يُنشئ زمن استجابة مرتفع غير مقبول. هذه هي المشكلة التي يحلّها تجميع الاتصالات (connection pooling)، وهو ما تعرضه واجهة DataSource.

DataSource — الأسلوب للإنتاج

تُعدّ javax.sql.DataSource (جزء من JDBC API) مصنعًا يوزّع الاتصالات المُنشأة مسبقًا من تجمّع (pool). من وجهة نظر الكود المُستدعي تكاد الواجهة البرمجية متطابقة — تستدعي dataSource.getConnection() بدلًا من DriverManager.getConnection(...) — لكن خلف الكواليس يأتي الاتصال من تجمّع قابل لإعادة الاستخدام لا من مقبس TCP جديد.

التنفيذ الأكثر استخدامًا لـ DataSource هو HikariCP، المعروف بتكلفته المنخفضة والتحقق الصارم منه. أضفه إلى ملف البناء:

<!-- Maven --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.1.0</version> </dependency>

إعداده واستخدامه:

import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class DataSourceFactory { private static final HikariDataSource DATA_SOURCE; static { HikariConfig cfg = new HikariConfig(); cfg.setJdbcUrl("jdbc:postgresql://localhost:5432/shop"); cfg.setUsername(System.getenv("DB_USER")); cfg.setPassword(System.getenv("DB_PASS")); // ضبط التجمّع cfg.setMaximumPoolSize(10); // الحد الأقصى للاتصالات المتزامنة cfg.setMinimumIdle(2); // إبقاء 2 نشطَين عند الهدوء cfg.setConnectionTimeout(30_000); // 30 ثانية للحصول على اتصال من التجمّع cfg.setIdleTimeout(600_000); // 10 دقائق قبل إغلاق الاتصال الخامل cfg.setMaxLifetime(1_800_000); // 30 دقيقة سقف صارم (يجب أن تكون < wait_timeout في قاعدة البيانات) cfg.setConnectionTestQuery("SELECT 1"); // استعلام التحقق من الصحة DATA_SOURCE = new HikariDataSource(cfg); } public static DataSource get() { return DATA_SOURCE; } }

يستعير كل مُستدعٍ الاتصالات ويعيدها عبر التجمّع:

try (Connection conn = DataSourceFactory.get().getConnection()) { // تنفيذ الاستعلامات } // conn.close() تُعيد الاتصال إلى التجمّع وليس إلى نظام التشغيل
استدع close() دائمًا (أو استخدم try-with-resources). مع التجمّع، لا تُدمّر close() مقبس TCP — بل تُعلّم الاتصال باعتباره متاحًا ثانيةً. نسيانها يُجفّف التجمّع تمامًا كما يفعل التسريب الحقيقي.

DriverManager مقابل DataSource — متى تستخدم أيًّا منهما

  • DriverManager: السكريبتات والأدوات التي تُنفَّذ مرة واحدة واختبارات الوحدة التي تُنشئ اتصالًا واحدًا. سهل الإعداد ولا يحتاج تبعيات فوق JAR التشغيل.
  • DataSource (مُجمَّع): كل تطبيق على جانب الخادم وكل خدمة تتعامل مع أكثر من طلب في آنٍ واحد. زمن استجابة أقل وحدود قابلة للضبط وفحص صحة وإيقاف تشغيل متحكَّم به.

عمليًا، تُهيّئ أُطر العمل كـ Spring Boot كائن DataSource مُجمَّعًا تلقائيًا من application.properties. إنّ فهم ما تُعدّه — HikariCP افتراضيًا — هو ما يُتيح لك ضبطه واستكشاف أخطائه.

الخلاصة

DriverManager أداتك للبدء السريع: أمدّه بعنوان URL واسم المستخدم وكلمة المرور واحصل على اتصال. لأي حمل حقيقي، استبدله بـ DataSource مُجمَّعة (HikariCP هو الاختيار المعياري). يعرض الاثنان كائن Connection الذي تستخدمه بشكل متطابق من هذه النقطة فصاعدًا — دائمًا داخل حقل try-with-resources. في الدرس القادم ستستخدم هذا الاتصال لتنفيذ جمل SQL.