تجميع الاتصالات وإدارة الموارد
تجميع الاتصالات وإدارة الموارد
كل كائن Connection تُنشئه عبر DriverManager.getConnection() يفتح مقبس TCP فعليًا إلى خادم قاعدة البيانات، ويُجري المصادقة، ويُخصّص ذاكرة على كلا الطرفين. تكلّف هذه الرحلة الذهاب والإياب عشرات إلى مئات الميليثانية. في تطبيق ويب يستقبل مئات الطلبات المتزامنة، إنشاء اتصال جديد لكل استعلام هو أسرع طريقة لإنهاك خادم قاعدة البيانات.
يحلّ تجميع الاتصالات (Connection Pooling) هذه المشكلة بالاحتفاظ بمخزن من الاتصالات المفتوحة مسبقًا، تُسلَّم للطالب وتُعاد إليه حين ينتهي — دون إغلاق المقبس الفعلي. تتمثّل النتيجة في تأخير أقل بكثير وموارد أقل على جانب قاعدة البيانات.
لماذا لا يكفي DriverManager للتوسّع
تأمّل دورة حياة اتصال مفتوح بـ DriverManager:
- يستدعي كودك
DriverManager.getConnection(url, user, pw). - يفتح برنامج تشغيل JDBC مقبس TCP إلى قاعدة البيانات.
- تُجري قاعدة البيانات المصادقة (10–100 ملليثانية).
- يُنفّذ كودك الاستعلام.
- يستدعي كودك
connection.close()فيُغلق المقبس.
الخطوات 1–3 و5 عبء صرف يفوق زمن الاستعلام الفعلي للاستعلامات القصيرة. تحت الضغط تنفد خانات الاتصال المتاحة أيضًا (الافتراضي في PostgreSQL 100، وكثير من الاستضافات المشتركة تسمح بأقل من ذلك).
HikariCP — المجمّع القياسي الفعلي
HikariCP هو أسرع وأكثر مجمّعات اتصالات JDBC استخدامًا على JVM. يعتمده Spring Boot افتراضيًا. إضافته إلى مشروع Maven تتطلّب تبعية واحدة:
إنشاء المجمّع وضبطه عند بدء التطبيق:
استخدام المجمّع — نفس الواجهة البرمجية بدون احتكاك
من منظور المستدعي، لا شيء يتغيّر. تستدعي dataSource.getConnection() بدلًا من DriverManager.getConnection()، وتستدعي connection.close() عند الانتهاء — لكن close() الآن تُعيد الاتصال إلى المجمّع بدلًا من تدميره.
try-with-resources — الإغلاق الصحيح لكل شيء
يستخدم JDBC ثلاثة موارد من نوع AutoCloseable: Connection، وStatement / PreparedStatement، وResultSet. الإخفاق في إغلاق أي منها يُسرّب المعالج الأساسي للنظام، وبالنسبة لـ Connection يُجفّف المجمّع.
عبارة try-with-resources المتاحة منذ Java 7 تضمن استدعاء close() على كل مورد معلَن بـترتيب عكسي للإعلان، حتى لو رُمي استثناء:
ResultSet مع Connection في رأس try واحد عندما تحتاج أولًا إلى استدعاء setters على الـ statement. قسّم إلى try خارجي لـ Connection وPreparedStatement، وtry داخلي لـ ResultSet.
ماذا يحدث عند نفاد المجمّع
إذا كانت جميع الاتصالات مُخصَّصة وجاء طلب جديد، يحجب dataSource.getConnection() حتى connectionTimeout ميليثانية ثم يرمي SQLException. هذا متعمَّد: يُنشئ ضغطًا عكسيًا يمنع تطبيقك من وضع آلاف الطلبات في الصف وتحطيم قاعدة البيانات بصمت.
دورة الحياة: مجمّع واحد للتطبيق مشترَك بين الخيوط
HikariDataSource آمن للخيوط (thread-safe). أنشئه مرة واحدة — في حقل ثابت، أو singleton، أو حاوية حقن التبعيات — وشاركه في كل مكان. إنشاء HikariDataSource جديد لكل طلب يُلغي كل فوائد التجميع.
DataSource (أو اعتمدت على الإعداد التلقائي بخصائص spring.datasource.*)، يُنشئ Spring مجمّع HikariCP ويُديره نيابةً عنك. ما عليك سوى حقن DataSource واستدعاء getConnection(). يُغلق المجمّع تلقائيًا عند إيقاف التطبيق.
الخلاصة
تُعيد استخدام تجميعات الاتصالات الاتصالات الفعلية بقاعدة البيانات، مختزلةً وقت الاتصال من ميليثانية لكل استعلام إلى ميكروثانية. HikariCP هو الخيار القياسي: اضبطه مرة واحدة عند البدء، واجعل maximumPoolSize محافظًا، واستخدم دائمًا try-with-resources لتُعاد الاتصالات والعبارات إلى المجمّع فور انتهاء استخدامها. الاتصال المُسرَّب الذي لا يُعاد أبدًا سيُجفّف المجمّع تحت الضغط فيُفشل جميع الطلبات اللاحقة — وعبارة try-with-resources تجعل هذا الصنف من الأخطاء مستحيلًا.