4. ملخص JPA
نقترح تقديم JPA (استمرارية Java API) مع بعض الأمثلة. تم تطوير JPA في الدورة التدريبية:
- استمرارية Java 5 من خلال الممارسة: [http://tahe.developpez.com/java/jpa] - يوفر الأدوات اللازمة لبناء طبقة الوصول إلى البيانات باستخدام JPA
4.1. مكانة JPA في بنية الطبقات
يُرجى من القارئ إعادة قراءة بداية هذا المستند (الفقرة 2) التي تشرح دور الطبقة JPA في بنية الطبقات. تندرج الطبقة JPA ضمن طبقات الوصول إلى البيانات:
![]() |
تتفاعل الطبقة [DAO] مع المواصفة JPA. بغض النظر عن المنتج الذي ينفذ هذه المواصفات، تظل واجهة الطبقة JPA المعروضة على الطبقة [DAO] كما هي. نقدم فيما يلي بعض الأمثلة المأخوذة من [ref1] والتي ستسمح لنا ببناء طبقتنا الخاصة JPA.
4.2. JPA - أمثلة
4.2.1. المثال 1 - تمثيل كائن لجدول واحد
4.2.1.1. الجدول [personne]
لنفترض أن لدينا قاعدة بيانات تحتوي على جدول واحد [personne]، وتتمثل مهمته في تخزين بعض المعلومات عن الأفراد:
![]() |
المفتاح الأساسي للجدول | |
إصدار السطر في الجدول. في كل مرة يتم فيها تعديل الشخص، يتم زيادة رقم الإصدار الخاص به. | |
اسم الشخص | |
اسمه الأول | |
تاريخ ميلاده | |
رقم صحيح 0 (غير متزوج) أو 1 (متزوج) | |
عدد أطفال الشخص |
4.2.1.2. الكيان [Personne]
نحن نضع أنفسنا في بيئة التنفيذ التالية:
![]() |
يجب أن تشكل الطبقة JPA [5] جسراً بين عالم العلاقات في قاعدة البيانات [7] وعالم الكائنات [4] الذي تعامله برامج Java [3]. يتم إنشاء هذا الجسر عن طريق التكوين، وهناك طريقتان للقيام بذلك:
- باستخدام ملفات XML. كانت هذه هي الطريقة الوحيدة تقريبًا للقيام بذلك حتى ظهور JDK 1.5
- باستخدام تعليقات Java منذ الإصدار JDK 1.5
في هذا المستند، سنستخدم الطريقة الثانية حصريًا.
قد يكون كائن [Personne] الخاص بالجدول [personne] المقدم سابقًا كما يلي:
...
@SuppressWarnings("unused")
@Entity
@Table(name="Personne")
public class Personne implements Serializable{
@Id
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name = "VERSION", nullable = false)
@Version
private int version;
@Column(name = "NOM", length = 30, nullable = false, unique = true)
private String nom;
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
@Column(name = "DATENAISSANCE", nullable = false)
@Temporal(TemporalType.DATE)
private Date datenaissance;
@Column(name = "MARIE", nullable = false)
private boolean marie;
@Column(name = "NBENFANTS", nullable = false)
private int nbenfants;
// الشركات المصنعة
public Personne() {
}
public Personne(String nom, String prenom, Date datenaissance, boolean marie,
int nbenfants) {
setNom(nom);
setPrenom(prenom);
setDatenaissance(datenaissance);
setMarie(marie);
setNbenfants(nbenfants);
}
// toString
public String toString() {
...
}
// المُستردات والمُعيّنات
...
}
يتم التكوين باستخدام تعليقات Java @Annotation. يتم استخدام تعليقات Java إما بواسطة المُجمِّع أو بواسطة أدوات متخصصة في وقت التنفيذ. باستثناء التعليق في السطر 3 المخصص للمُجمِّع، فإن جميع التعليقات هنا مخصصة للتنفيذ JPA المستخدم، Hibernate أو Toplink. وبالتالي سيتم استخدامها عند التنفيذ. في حالة عدم وجود أدوات قادرة على تفسيرها، يتم تجاهل هذه التعليقات التوضيحية. وبالتالي، يمكن استخدام الفئة [Personne] المذكورة أعلاه في سياق خارج JPA.
يجب التمييز بين حالتين لاستخدام التعليقات التوضيحية JPA في فئة C مرتبطة بجدول T:
- الجدول T موجود بالفعل: يجب أن تعكس التعليقات التوضيحية JPA ما هو موجود بالفعل (اسم الأعمدة وتعريفها، قيود التكامل، المفاتيح الخارجية، المفاتيح الأساسية، ...)
- الجدول T غير موجود وسيتم إنشاؤه بناءً على التعليقات التوضيحية الموجودة في الفئة C.
الحالة 2 هي الأسهل في التعامل معها. باستخدام التعليقات التوضيحية JPA، نحدد بنية الجدول T التي نريدها. غالبًا ما تكون الحالة 1 أكثر تعقيدًا. ربما تم إنشاء الجدول T منذ فترة طويلة خارج أي سياق JPA. قد تكون بنيتها غير ملائمة للجسر العلائقي / الكائني لـ JPA. للتبسيط، سنضع أنفسنا في الحالة 2 حيث سيتم إنشاء الجدول T المرتبط بالفئة C وفقًا للتعليقات التوضيحية JPA الخاصة بالفئة C.
دعونا نعلق على تعليقات JPA الخاصة بالفئة [Personne]:
- السطر 4: التعليق التوضيحي @Entity هو أول تعليق توضيحي ضروري. يتم وضعه قبل السطر الذي يعلن عن الفئة ويشير إلى أن الفئة المعنية يجب أن تدار بواسطة طبقة الاستمرارية JPA. في حالة عدم وجود هذا التعليق التوضيحي، سيتم تجاهل جميع التعليقات التوضيحية الأخرى JPA.
- السطر 5: تشير العلامة @Table إلى جدول قاعدة البيانات الذي تمثل الفئة نسخة منه. الحجة الرئيسية لها هي name التي تشير إلى اسم الجدول. وفي حالة عدم وجود هذه الحجة، سيحمل الجدول اسم الفئة، وهو هنا [Personne]. وفي مثالنا هذا، تكون العلامة @Table غير ضرورية.
- السطر 8: تُستخدم العلامة @Id لتحديد الحقل في الفئة الذي يمثل المفتاح الأساسي للجدول. هذه العلامة إلزامية. وهي تشير هنا إلى أن الحقل id في السطر 11 يمثل المفتاح الأساسي للجدول.
- السطر 9: تُستخدم العلامة @Column لربط حقل في الفئة بالعمود في الجدول الذي يمثله الحقل. يشير السمة name إلى اسم العمود في الجدول. في حالة عدم وجود هذه السمة، يحمل العمود نفس اسم الحقل. في مثالنا، لم يكن الحجة name إلزامية. تشير الحجة nullable=false إلى أن العمود المرتبط بالحقل لا يمكن أن يكون له القيمة NULL، وبالتالي يجب أن يكون للحقل قيمة بالضرورة.
- السطر 10: تشير التعليقات التوضيحية @GeneratedValue إلى كيفية إنشاء المفتاح الأساسي عندما يتم إنشاؤه تلقائيًا بواسطة SGBD. وسيكون هذا هو الحال في جميع أمثلةنا. هذا ليس إلزاميًا. وبالتالي، قد يكون لشخصنا رقم طالب يستخدم كمفتاح أساسي ولا يتم إنشاؤه بواسطة SGBD بل يتم تحديده بواسطة التطبيق. في هذه الحالة، سيكون التعليق التوضيحي @GeneratedValue غائبًا. تشير الحجة strategy إلى كيفية إنشاء المفتاح الأساسي عندما يتم إنشاؤه بواسطة SGBD. لا تستخدم جميع SGBD نفس تقنية إنشاء قيم المفتاح الأساسي. على سبيل المثال:
يستخدم مولد قيم يتم استدعاؤه قبل كل عملية إدراج | |
يتم تعريف حقل المفتاح الأساسي على أنه من النوع Identity. نحصل على نتيجة مشابهة لمولد قيم Firebird، باستثناء أن قيمة المفتاح لا تُعرف إلا بعد إدراج السطر. | |
يستخدم كائنًا يسمى SEQUENCE والذي يلعب دور مولد القيم مرة أخرى |
يجب أن تقوم الطبقة JPA بتوليد أوامر SQL مختلفة وفقًا لـ SGBD لإنشاء مولد القيم. يتم تحديد نوع SGBD الذي يجب أن تديره من خلال التكوين. وبالتالي، يمكنها معرفة الاستراتيجية المعتادة لتوليد قيم المفتاح الأساسي لهذا SGBD. تشير الحجة strategy = GenerationType.AUTO إلى الطبقة JPA بأن عليها استخدام هذه الاستراتيجية المعتادة. نجحت هذه التقنية في جميع أمثلة هذا المستند بالنسبة لـ SGBD السبعة المستخدمة.
- السطر 14: تشير التعليقات التوضيحية @Version إلى الحقل المستخدم لإدارة الوصول المتزامن إلى نفس السطر في الجدول.
لفهم مشكلة الوصول المتزامن إلى نفس السطر في الجدول [personne]، لنفترض أن تطبيق ويب يسمح بتحديث شخص ما ولندرس الحالة التالية:
في الوقت T1، يدخل مستخدم U1 لتعديل شخص P. في هذه اللحظة، يكون عدد الأبناء 0. يقوم بتغيير هذا العدد إلى 1 ولكن قبل أن يثبت تعديله، يدخل مستخدم U2 لتعديل نفس الشخص P. وبما أن U1 لم يثبت تعديله بعد، يرى U2 على شاشته أن عدد الأطفال هو 0. يقوم U2 بتغيير اسم الشخص P إلى أحرف كبيرة. ثم يقوم U1 و U2 بتأكيد تعديلاتهما بهذا الترتيب. التعديل الذي أجراه U2 هو الذي سيسود: في قاعدة البيانات، سيتحول الاسم إلى أحرف كبيرة وسيبقى عدد الأطفال صفرًا، حتى وإن كان U1 يعتقد أنه قد غيّره إلى 1.
يساعدنا مفهوم إصدار الشخص في حل هذه المشكلة. لنأخذ نفس حالة الاستخدام:
في الوقت T1، يدخل مستخدم U1 لتعديل شخص P. في هذه اللحظة، يكون عدد الأبناء 0 والإصدار V1. يقوم بتغيير عدد الأبناء إلى 1 ولكن قبل أن يثبت تعديله، يدخل مستخدم U2 لتعديل نفس الشخص P. وبما أن U1 لم يثبت تعديله بعد، يرى U2 أن عدد الأطفال هو 0 والإصدار هو V1. يقوم U2 بتغيير اسم الشخص P إلى أحرف كبيرة. ثم يقوم كل من U1 و U2 بالتصديق على تعديلاتهما بهذا الترتيب. قبل التصديق على أي تعديل، يتم التحقق من أن الشخص الذي يقوم بتعديل الشخص P يمتلك نفس الإصدار الذي يمتلكه الشخص P المسجل حاليًا. سيكون هذا هو الحال بالنسبة للمستخدم U1. وبالتالي يتم قبول تعديله، ثم يتم تغيير إصدار الشخص المعدل من V1 إلى V2 للإشارة إلى أن الشخص قد خضع لتغيير. عند المصادقة على تعديل U2، سنلاحظ أن U2 يمتلك إصدار V1 للشخص P، في حين أن إصداره الحالي هو V2. سنتمكن عندئذٍ من إخبار المستخدم U2 بأن شخصًا ما سبقه وأن عليه البدء من الإصدار الجديد للشخص P. وسيقوم بذلك، وسيحصل على شخص P من الإصدار V2 الذي أصبح لديه الآن طفل، وسيحول الاسم إلى أحرف كبيرة، ثم يوافق. سيتم قبول تعديله إذا كان الشخص P المسجل لا يزال يحمل الإصدار V2. في النهاية، سيتم أخذ التعديلات التي أجراها U1 و U2 في الاعتبار، بينما في حالة الاستخدام بدون إصدار، كان أحد التعديلات قد ضاع.
يمكن للطبقة [DAO] في تطبيق العميل إدارة إصدار الفئة [Personne] بنفسها. في كل مرة يتم فيها تعديل كائن P، سيتم زيادة إصدار هذا الكائن بمقدار 1 في الجدول. تسمح التعليقات التوضيحية @Version بنقل هذه الإدارة إلى الطبقة JPA. لا يلزم أن يُسمى الحقل المعني version كما في المثال. يمكن أن يحمل أي اسم.
الحقول المطابقة للتعليقات التوضيحية @Id و@Version هي حقول موجودة بسبب الاستمرارية. لن تكون هناك حاجة إليها إذا لم تكن الفئة [Personne] بحاجة إلى الاستمرارية. نرى إذن أن الكائن لا يتم تمثيله بنفس الطريقة حسب ما إذا كان بحاجة إلى الاستمرارية أم لا.
- السطر 17: مرة أخرى التعليق التوضيحي @Column لتقديم معلومات عن عمود الجدول [personne] المرتبط بالحقل nom في الفئة Personne. نجد هنا حجتين جديدتين:
- unique=true تشير إلى أن اسم الشخص يجب أن يكون فريدًا. سيترجم ذلك في قاعدة البيانات بإضافة قيد فريد على العمود NOM في الجدول [personne].
- يحدد length=30 عدد أحرف العمود NOM بـ 30 حرفًا. وهذا يعني أن نوع هذا العمود سيكون VARCHAR(30).
- السطر 24: تُستخدم التعليقات التوضيحية @Temporal للإشارة إلى نوع SQL الذي يجب إعطاؤه لعمود/حقل من نوع التاريخ/الوقت. يشير النوع TemporalType.DATE إلى تاريخ فقط دون وقت مرتبط به. الأنواع الأخرى الممكنة هي TemporalType.TIME لترميز الساعة و TemporalType.TIMESTAMP لترميز التاريخ مع الساعة.
لنعلق الآن على بقية كود الفئة [Personne]:
- السطر 6: تنفذ الفئة واجهة Serializable. تتكون sérialisation لكائن ما من تحويله إلى سلسلة من البتات. désérialisation هي العملية العكسية. يُستخدم التسلسل/إلغاء التسلسل بشكل خاص في تطبيقات العميل/الخادم حيث يتم تبادل الكائنات عبر الشبكة. لا تعلم تطبيقات العميل أو الخادم بهذه العملية التي تتم بشكل شفاف بواسطة JVM. ولكن لكي تكون هذه العملية ممكنة، يجب أن تكون فئات الكائنات المتبادلة "موسومة" بالكلمة الرئيسية Serializable.
- السطر 37: منشئ للفئة. تجدر الإشارة إلى أن الحقلين id و version ليسا جزءًا من المعلمات. في الواقع، يتم إدارة هذين الحقلين بواسطة الطبقة JPA وليس بواسطة التطبيق.
- السطر 51 وما بعده: طريقتا get و set لكل حقل من حقول الفئة. تجدر الإشارة إلى أنه يمكن وضع التعليقات التوضيحية JPA على طرق get الخاصة بالحقول بدلاً من وضعها على الحقول نفسها. يحدد مكان التعليقات التوضيحية الوضع الذي يجب أن تستخدمه JPA للوصول إلى الحقول:
- إذا تم وضع التعليقات التوضيحية على مستوى الحقل، فسيصل JPA مباشرة إلى الحقول لقراءتها أو كتابتها
- إذا تم وضع التعليقات التوضيحية على مستوى get، فسيصل JPA إلى الحقول عبر طرق get / set لقراءتها أو كتابتها
إن موضع التعليق التوضيحي @Id هو الذي يحدد موضع التعليقات التوضيحية JPA لفئة ما. عند وضعها على مستوى الحقل، فإنها تشير إلى وصول مباشر إلى الحقول، وعند وضعها على مستوى get، فإنها تشير إلى الوصول إلى الحقول عبر get و set. يجب عندئذٍ وضع التعليقات التوضيحية الأخرى بنفس طريقة وضع التعليق التوضيحي @Id.
4.2.2. تكوين الطبقة JPA
يمكن إجراء اختبارات الطبقة JPA باستخدام البنية التالية:
![]() |
- في [7]: قاعدة البيانات التي سيتم إنشاؤها من تعليقات الكيان [Personne] بالإضافة إلى التكوينات الإضافية التي تم إجراؤها في ملف يسمى [persistence.xml]
- إلى [5, 6]: طبقة JPA تم تنفيذها بواسطة Hibernate
- في [4]: الكيان [Personne]
- إلى [3]: برنامج اختبار من نوع وحدة التحكم
يتم تكوين الطبقة JPA بواسطة الملف [META-INF/persistence.xml]:
![]() |
عند التنفيذ، يتم البحث عن الملف [META-INF/persistence.xml] في ملف Classpath الخاص بالتطبيق.
دعونا نلقي نظرة على تكوين الطبقة JPA الموجود في الملف [persistence.xml] لمشروعنا:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
<!-- المزود -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<!-- الفئات الدائمة -->
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- السجلات SQL
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
-->
<!-- الاتصال JDBC -->
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
<property name="hibernate.connection.username" value="jpa" />
<property name="hibernate.connection.password" value="jpa" />
<!-- إنشاء المخطط تلقائيًا -->
<property name="hibernate.hbm2ddl.auto" value="create" />
<!-- اللهجة -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<!-- خصائص DataSource c3p0 -->
<property name="hibernate.c3p0.min_size" value="5" />
<property name="hibernate.c3p0.max_size" value="20" />
<property name="hibernate.c3p0.timeout" value="300" />
<property name="hibernate.c3p0.max_statements" value="50" />
<property name="hibernate.c3p0.idle_test_period" value="3000" />
</properties>
</persistence-unit>
</persistence>
لفهم هذا التكوين، علينا العودة إلى بنية الوصول إلى البيانات في تطبيقنا:
![]() |
- سيقوم الملف [persistence.xml] بتكوين الطبقات [4, 5, 6]
- [4]: تنفيذ Hibernate لـ JPA
- [5]: يصل Hibernate إلى قاعدة البيانات عبر مجموعة اتصالات. مجموعة الاتصالات هي احتياطي من الاتصالات المفتوحة مع SGBD. يتم الوصول إلى SGBD من قبل العديد من المستخدمين، في حين أنه لأسباب تتعلق بالأداء، لا يمكن أن يتجاوز عددًا محددًا N من الاتصالات المفتوحة في وقت واحد. يفتح الكود المكتوب بشكل جيد اتصالاً مع SGBD لأقل وقت ممكن: فهو يصدر أوامر SQL ويغلق الاتصال. وسيقوم بذلك بشكل متكرر، في كل مرة يحتاج فيها إلى العمل مع قاعدة البيانات. تكلفة فتح/إغلاق اتصال ليست بالهينة، وهنا يأتي دور مجموعة الاتصالات. ستقوم هذه المجموعة عند بدء تشغيل التطبيق بفتح N1 اتصالات مع SGBD. وستطلب التطبيق منه اتصالاً مفتوحاً عندما تحتاج إليه. وسيتم إعادة هذا الاتصال إلى المجمع بمجرد أن لا يعود التطبيق بحاجة إليه، ويفضل أن يكون ذلك في أسرع وقت ممكن. لا يتم إغلاق الاتصال ويبقى متاحاً للمستخدم التالي. وبالتالي، فإن مجمع الاتصالات هو نظام لمشاركة الاتصالات المفتوحة.
- [6]: برنامج التشغيل JDBC المستخدم لـ SGBD
الآن لنرى كيف يقوم الملف [persistence.xml] بتكوين الطبقات [4, 5, 6] المذكورة أعلاه:
- السطر 2: العلامة الجذرية لملف XML هي <persistence>.
- السطر 3: <persistence-unit> تُستخدم لتعريف وحدة استمرارية. يمكن أن يكون هناك عدة وحدات استمرارية. لكل منها اسم (السمة name) ونوع معاملات (السمة transaction-type). سيتمكن التطبيق من الوصول إلى وحدة الاستمرارية عبر اسمها، وهو jpa هنا. يشير نوع المعاملة RESOURCE_LOCAL إلى أن التطبيق يدير المعاملات بنفسه باستخدام SGBD. سيكون هذا هو الحال هنا. عندما يتم تشغيل التطبيق في حاوية EJB3، يمكنه استخدام خدمة المعاملات الخاصة بها. في هذه الحالة، سنضع transaction-type=JTA (معاملة Java API). JTA هي القيمة الافتراضية عند عدم وجود السمة transaction-type.
- السطر 5: تُستخدم علامة <provider> لتعريف فئة تُنفِّذ واجهة [javax.persistence.spi.PersistenceProvider]، وهي واجهة تسمح للتطبيق بتهيئة طبقة الاستمرارية. نظرًا لاستخدامنا لتنفيذ JPA / Hibernate، فإن الفئة المستخدمة هنا هي فئة من Hibernate.
- السطر 6: تدرج العلامة <properties> خصائص خاصة بـ provider المحدد. وبالتالي، حسب ما إذا تم اختيار Hibernate أو Toplink أو Kodo...، فستكون هناك خصائص مختلفة. الخصائص التالية خاصة بـ Hibernate.
- السطر 8: يطلب من Hibernate استكشاف classpath الخاص بالمشروع للعثور على الفئات التي تحتوي على التعليق التوضيحي @Entity من أجل إدارتها. يمكن أيضًا إعلان فئات @Entity بواسطة علامات <class>nom_de_la_classe</class>، مباشرةً تحت علامة <persistence-unit>. وهذا ما سنفعله مع provider JPA / Toplink.
- تقوم الأسطر 10-12، الموضوعة هنا في تعليقات، بتكوين سجلات وحدة التحكم في Hibernate:
- السطر 10: لتحديد ما إذا كان سيتم عرض الأوامر التي يصدرها Hibernate على SQL أم لا. وهذا مفيد جدًا خلال مرحلة التعلم. بسبب الجسر العلائقي / الكائني، تعمل التطبيق على كائنات ثابتة تطبق عليها عمليات من نوع [persist, merge, remove]. من المثير للاهتمام معرفة الأوامر SQL التي يتم إصدارها فعليًا على هذه العمليات. من خلال دراستها، نتمكن تدريجيًا من تخمين الأوامر SQL التي سيقوم Hibernate بتوليدها عند إجراء مثل هذه العملية على الكائنات الدائمة، ويبدأ الجسر العلائقي/الكائني في التبلور في الذهن.
- السطر 11: يمكن تنسيق الأوامر SQL المعروضة على وحدة التحكم بشكل جميل لتسهيل قراءتها
- السطر 12: سيتم أيضًا إضافة تعليقات على الأوامر SQL المعروضة
- تحدد الأسطر 15-19 الطبقة JDBC (الطبقة [6] في البنية):
- السطر 15: فئة برنامج التشغيل JDBC لـ SGBD، هنا MySQL5
- السطر 16: عنوان URL لقاعدة البيانات المستخدمة
- السطران 17 و 18: مستخدم الاتصال وكلمة المرور الخاصة به
- السطر 22: يحتاج Hibernate إلى معرفة SGBD الذي أمامه. في الواقع، تحتوي جميع SGBD على امتدادات SQL خاصة، وهي طريقة خاصة لإدارة التوليد التلقائي لقيم مفتاح أساسي، ... مما يجعل Hibernate بحاجة إلى معرفة SGBD الذي يعمل معه من أجل إرسال الأوامر SQL التي سيفهمها هذا الأخير. يشير [MySQL5InnoDBDialect] إلى SGBD MySQL5 مع جداول من النوع InnoDB التي تدعم المعاملات.
- تقوم الأسطر 24-28 بتكوين مجموعة الاتصالات c3p0 (الطبقة [5] في البنية):
- السطران 24 و25: الحد الأدنى (الافتراضي 3) والحد الأقصى لعدد الاتصالات (الافتراضي 15) في المجموعة. العدد الافتراضي الأولي للاتصالات هو 3.
- السطر 26: المدة القصوى بالمللي ثانية لانتظار طلب اتصال من العميل. بعد انقضاء هذه المدة، سيرسل c3p0 استثناءً إليه.
- السطر 27: للوصول إلى BD، يستخدم Hibernate أوامر SQL المعدة مسبقًا (PreparedStatement) التي يمكن لـ c3p0 تخزينها في ذاكرة التخزين المؤقت. وهذا يعني أنه إذا طلب التطبيق مرة ثانية أمر SQL مُعد مسبقًا وموجود بالفعل في ذاكرة التخزين المؤقت، فلن تكون هناك حاجة لإعداده (إعداد أمر SQL له تكلفة) وسيتم استخدام الأمر الموجود في ذاكرة التخزين المؤقت. هنا، يتم تحديد العدد الأقصى لأوامر SQL المعدة التي يمكن أن تحتوي عليها ذاكرة التخزين المؤقت، لجميع الاتصالات مجتمعة (تنتمي أمر SQL المعد إلى اتصال واحد).
- السطر 28: تكرار التحقق من صلاحية الاتصالات بالمللي ثانية. قد تصبح إحدى اتصالات المجموعة غير صالحة لأسباب متنوعة (يقوم برنامج التشغيل JDBC بإبطال صلاحية الاتصال لأنه طويل جدًا، أو يعاني برنامج التشغيل JDBC من "أخطاء"، ...).
- السطر 20: يُطلب هنا، عند تهيئة وحدة الاستمرارية، إنشاء قاعدة بيانات صورة الكائنات @Entity. يمتلك Hibernate الآن جميع الأدوات اللازمة لإصدار أوامر SQL لإنشاء جداول قاعدة البيانات:
- تسمح له تكوين كائنات @Entity بمعرفة الجداول التي يجب إنشاؤها
- تسمح له الأسطر 15-18 و24-28 بالحصول على اتصال مع SGBD
- تسمح له السطر 22 بمعرفة اللهجة SQL التي يجب استخدامها لإنشاء الجداول
وبالتالي، فإن الملف [persistence.xml] المستخدم هنا يعيد إنشاء قاعدة بيانات جديدة عند كل تشغيل جديد للتطبيق. يتم إعادة إنشاء الجداول (create table) بعد إزالتها (drop table) في حالة وجودها. تجدر الإشارة إلى أنه من الواضح أنه لا ينبغي القيام بذلك مع قاعدة بيانات قيد التشغيل...
4.2.3. مثال 2: علاقة واحد إلى عدة
4.2.3.1. مخطط قاعدة البيانات
1 ![]() | 2 |
- في [1]، قاعدة البيانات وفي [2]، DDL (MySQL5)
ينتمي عنصر A(id, version, name) إلى فئة C(id, version, name) واحدة فقط. يمكن أن تحتوي فئة C على 0 أو 1 أو عدة عناصر. لدينا علاقة واحد إلى عدة (فئة -> عنصر) والعلاقة العكسية عدة إلى واحد (عنصر -> فئة). تتجسد هذه العلاقة من خلال المفتاح الأجنبي الذي تمتلكه الجدول [article] في الجدول [categorie] (السطور 24-28 من DDL).
4.2.3.2. كائنات @Entity التي تمثل قاعدة البيانات
يتم تمثيل المقالة بواسطة @Entity [Article] التالية:
package entites;
...
@Entity
@Table(name="jpa05_hb_article")
public class Article implements Serializable {
// الحقول
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// العلاقة الرئيسية Article (many) -> Category (one)
// مُنفَّذة بواسطة مفتاح خارجي (categorie_id) في المقالة
// كل منتج له بالضرورة فئة واحدة (nullable=false)
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "categorie_id", nullable = false)
private Categorie categorie;
// منشئات
public Article() {
}
// مُستردات ومُعيّنات
...
// toString
public String toString() {
return String.format("Article[%d,%d,%s,%d]", id, version, nom, categorie.getId());
}
}
- الأسطر 9-11: المفتاح الأساسي لـ @Entity
- الأسطر 13-15: رقم إصداره
- السطران 17-18: اسم المادة
- الأسطر 20-25: علاقة «عدة إلى واحد» تربط الكيان @Entity Article بالكيان @Entity Categorie:
- السطر 23: التعليق التوضيحي ManyToOne. يشير Many إلى @Entity Article الذي نحن فيه، ويشير One إلى @Entity Categorie (السطر 25). يمكن أن تحتوي فئة (One) على عدة عناصر (Many).
- السطر 24: التعليق التوضيحي ManyToOne يحدد عمود المفتاح الأجنبي في الجدول [article]. وستسمى (name) categorie_id ويجب أن تحتوي كل سطر على قيمة في هذا العمود (nullable=false).
- السطر 25: الفئة التي ينتمي إليها العنصر. عندما يتم وضع عنصر في سياق الاستمرارية، يُطلب ألا يتم وضع فئته فيه على الفور (fetch=FetchType.LAZY، السطر 23). لا نعرف ما إذا كان لهذا الطلب معنى. سنرى.
يتم تمثيل الفئة بواسطة @Entity [Categorie] التالية:
package entites;
...
@Entity
@Table(name="jpa05_hb_categorie")
public class Categorie implements Serializable {
// الحقول
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// العلاقة العكسية فئة (واحدة) -> مقال (عدة) من العلاقة مقال (عدة) -> فئة (واحدة)
// إدراج متسلسل فئة -> إدراج مقالات
// تسلسل تحديث الفئة -> تحديث المقالات
// التسلسل التسلسلي للحذف فئة -> حذف مقالات
@OneToMany(mappedBy = "categorie", cascade = { CascadeType.ALL })
private Set<Article> articles = new HashSet<Article>();
// منشئون
public Categorie() {
}
// المُستردات والمُعيّنات
...
// toString
public String toString() {
return String.format("Categorie[%d,%d,%s]", id, version, nom);
}
// الارتباط الثنائي الاتجاه الفئة <--> المقالة
public void addArticle(Article article) {
// يتم إضافة المقالة إلى مجموعة مقالات الفئة
articles.add(article);
// يتم تغيير فئة المقالة
article.setCategorie(this);
}
}
- الأسطر 8-11: المفتاح الأساسي لـ @Entity
- الأسطر 12-14: إصدارها
- السطور 16-17: اسم الفئة
- الأسطر 19-24: مجموعة (set) مقالات الفئة
- السطر 23: تشير التعليقات التوضيحية @OneToMany إلى علاقة واحد إلى عدة. يشير One إلى @Entity [Categorie] التي نحن فيها، ويشير Many إلى النوع [Article] في السطر 24: فئة واحدة (One) لها عدة (Many) مقالات.
- السطر 23: التعليق هو عكس (mappedBy) التعليق ManyToOne الموضوع على الحقل categorie في الكيان @Entity Article: mappedBy=categorie. العلاقة ManyToOne الموضوعة على الحقل categorie في الكيان @Entity Article هي العلاقة الرئيسية. وهي ضرورية. وهي تجسد علاقة المفتاح الأجنبي التي تربط @Entity Article بـ @Entity Categorie. العلاقة OneToMany الموضوعة على الحقل articles في @Entity Categorie هي العلاقة العكسية. وهي ليست ضرورية. وهي وسيلة ملائمة للحصول على المقالات من فئة معينة. وبدون هذه الوسيلة، سيتم الحصول على هذه المقالات من خلال استعلام JPQL.
- السطر 23: تطلب cascadeType.ALL أن يتم تطبيق العمليات (persist، merge، remove) التي يتم إجراؤها على @Entity Categorie بشكل متسلسل على مقالاتها.
- السطر 24: سيتم وضع العناصر في فئة ما في كائن من نوع Set<Article>. لا يقبل النوع Set التكرارات. وبالتالي لا يمكن وضع نفس العنصر مرتين في كائن Set<Article>. ماذا يعني "نفس العنصر"؟ للقول بأن المقالة a هي نفس المقالة b، تستخدم Java التعبير a.equals(b). في فئة Object، الأم لجميع الفئات، يكون a.equals(b) صحيحًا إذا كان a==b، c.a.d. إذا كان للكائنين a و b نفس موقع الذاكرة. قد نرغب في القول بأن العنصرين a و b متماثلان إذا كان لهما نفس الاسم. في هذه الحالة، يجب على المطور إعادة تعريف طريقتين في الفئة [Article]:
- equals: يجب أن تُرجع القيمة "صحيح" إذا كان الاسمان متطابقين
- hashCode: يجب أن تُرجع قيمة عددية متطابقة لكائنين [Article] تعتبرهما الطريقة equals متساويين. هنا، سيتم بناء القيمة بناءً على اسم العنصر. يمكن أن تكون القيمة التي تعطيها hashCode أي عدد صحيح. وتُستخدم في حاويات كائنات مختلفة، لا سيما القواميس (Hashtable).
يمكن للعلاقة OneToMany استخدام أنواع أخرى غير Set لتخزين Many، مثل كائنات List على سبيل المثال. لن نتطرق إلى هذه الحالات في هذا المستند. سيجد القارئ هذه الحالات في [ref1].
- السطر 38: تسمح لنا الطريقة [addArticle] بإضافة مقال إلى فئة. تقوم الطريقة بتحديث طرفي العلاقة OneToMany التي تربط [Categorie] بـ [Article].
4.3. API من الطبقة JPA
دعونا نوضح بيئة تشغيل عميل JPA:
![]() |
نعلم أن الطبقة JPA [2] تنشئ جسرًا بين الكائن [3] والعلاقة [4]. يُطلق على مجموعة الكائنات التي تديرها الطبقة JPA في إطار هذا الجسر بين الكائنات والعلاقات اسم "سياق الاستمرارية". للوصول إلى بيانات سياق الاستمرارية، يجب على عميل JPA [1] المرور عبر الطبقة JPA [2]:
- يمكنه إنشاء كائن وطلب من الطبقة JPA جعله ثابتًا. يصبح الكائن عندئذ جزءًا من سياق الثبات.
- يمكنه أن يطلب من الطبقة [JPA] مرجعًا لكائن ثابت موجود.
- يمكنه تعديل كائن دائم تم الحصول عليه من الطبقة JPA.
- يمكنه أن يطلب من الطبقة JPA حذف كائن من سياق الاستمرارية.
تقدم الطبقة JPA للعميل واجهة تسمى [EntityManager] والتي، كما يوحي اسمها، تسمح بإدارة كائنات @Entity في سياق الاستمرارية. نعرض أدناه الطرق الرئيسية لهذه الواجهة:
تضع entity في سياق الاستمرارية | |
يزيل entity من سياق الاستمرارية | |
يدمج كائن entity من العميل غير المدار بواسطة سياق الاستمرارية مع الكائن entity من سياق الاستمرارية الذي له نفس المفتاح الأساسي. والنتيجة هي الكائن entity من سياق الاستمرارية. | |
يضع في سياق الاستمرارية كائنًا تم البحث عنه في قاعدة البيانات عبر مفتاحه الأساسي. يسمح النوع T للكائن لطبقة JPA معرفة الجدول الذي يجب الاستعلام عنه. يتم إرجاع الكائن الدائم الذي تم إنشاؤه بهذه الطريقة إلى العميل. | |
يُنشئ كائن Query من استعلام JPQL (Java Persistence Query Language). استعلام JPQL مشابه لاستعلام SQL إذا باستثناء أنه يستعلم عن الكائنات بدلاً من الجداول. | |
طريقة مشابهة للطريقة السابقة، باستثناء أن queryText هي الأمر SQL وليس JPQL. | |
طريقة مماثلة لـ createQuery، باستثناء أن الأمر JPQL queryText قد تم نقله إلى ملف تكوين وربطه باسم. وهذا الاسم هو معلمة الطريقة. |
يتمتع الكائن EntityManager بدورة حياة ليست بالضرورة هي نفس دورة حياة التطبيق. له بداية ونهاية. وبالتالي، يمكن لعميل JPA العمل بالتتابع مع كائنات EntityManager مختلفة. سياق الاستمرارية المرتبط بـ EntityManager له نفس دورة الحياة التي يتمتع بها. وهما لا ينفصلان عن بعضهما البعض. عندما يتم إغلاق كائن EntityManager، يتم مزامنة سياق الاستمرارية الخاص به مع قاعدة البيانات إذا لزم الأمر، ثم يختفي. يجب إنشاء كائن EntityManager جديد للحصول على سياق استمرارية جديد.
يمكن للعميل JPA إنشاء كائن EntityManager وبالتالي سياق استمرارية باستخدام الأمر التالي:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("nom d'une unité de persistance");
- javax.persistence.Persistence هي فئة ثابتة تسمح بالحصول على مصنع (factory) لكائنات EntityManager. ويرتبط هذا المصنع بوحدة ثبات محددة. نتذكر أن ملف التكوين [META-INF/persistence.xml] يسمح بتعريف وحدات الاستمرارية وأن لهذه الوحدات أسماء:
<persistence-unit name="elections-dao-jpa-mysql-01PU" transaction-type="RESOURCE_LOCAL">
فيما سبق، تسمى وحدة الاستمرارية elections-dao-jpa-mysql-01PU. وتأتي معها تكوينات خاصة بها، لا سيما SGBD التي تعمل معها. تقوم التعليمات [Persistence.createEntityManagerFactory("elections-dao-jpa-mysql-01PU")] بإنشاء مصنع كائنات من النوع EntityManagerFactory قادر على توفير كائنات EntityManager المخصصة لإدارة سياقات الاستمرارية المرتبطة بوحدة الاستمرارية المسماة elections-dao-jpa-mysql-01PU. يتم الحصول على كائن EntityManager وبالتالي على سياق استمرارية من الكائن EntityManagerFactory بالطريقة التالية:
تسمح الطرق التالية للواجهة [EntityManager] بإدارة دورة حياة سياق الاستمرارية:
يتم إغلاق سياق الاستمرارية. يفرض مزامنة سياق الاستمرارية مع قاعدة البيانات:
| |
يتم إفراغ سياق الاستمرارية من جميع كائناته ولكن لا يتم إغلاقه. | |
يتم مزامنة سياق الاستمرارية مع قاعدة البيانات بالطريقة الموضحة لـ close() |
يمكن للعميل JPA فرض مزامنة سياق الاستمرارية مع قاعدة البيانات باستخدام الطريقة [EntityManager].flush السابقة. يمكن أن تكون المزامنة صريحة أو ضمنية. في الحالة الأولى، يتعين على العميل إجراء عمليات flush عندما يرغب في إجراء عمليات المزامنة، وإلا فإن هذه العمليات تتم في أوقات معينة سنحددها لاحقًا. يتم إدارة وضع المزامنة من خلال الطرق التالية للواجهة [EntityManager]:
هناك قيمتان محتملتان لـ flushmode: FlushModeType.AUTO (الافتراضي): تتم المزامنة قبل كل استعلام يتم إجراؤه على قاعدة البيانات. FlushModeType.COMMIT: لا تتم المزامنة إلا نهاية المعاملات في قاعدة البيانات. | |
يعرض وضع المزامنة الحالي |
لنلخص. في الوضع FlushModeType.AUTO، وهو الوضع الافتراضي، سيتم مزامنة سياق الاستمرارية مع قاعدة البيانات في الأوقات التالية:
- قبل كل عملية SELECT على قاعدة البيانات
- في نهاية معاملة على قاعدة البيانات
- بعد إجراء عملية flush أو close على سياق الاستمرارية
في الوضع FlushModeType.COMMIT، الأمر نفسه باستثناء العملية 1 التي لا تحدث. الوضع العادي للتفاعل مع الطبقة JPA هو وضع معاملاتي. يقوم العميل بعمليات متنوعة على سياق الاستمرارية، داخل معاملة. في هذه الحالة، تكون لحظات مزامنة سياق الاستمرارية مع قاعدة البيانات هي الحالتان 1 و 2 أعلاه في الوضع AUTO، والحالة 2 فقط في الوضع COMMIT.
نختتم بـ API من واجهة Query، وهي واجهة تسمح بإصدار أوامر JPQL على سياق الاستمرارية أو أوامر SQL مباشرة على قاعدة البيانات لاسترداد البيانات منها. واجهة الاستعلام هي كما يلي:
![]() |
- 1 - تقوم الطريقة getResultList بتنفيذ SELECT التي تعيد عدة كائنات. سيتم الحصول على هذه الكائنات في كائن List. هذا الكائن هو واجهة. توفر هذه الواجهة كائن Iterator الذي يسمح بتصفح عناصر القائمة L بالشكل التالي:
Iterator iterator = L.iterator();
while (iterator.hasNext()) {
// استخدام الكائن iterator.next() الذي يمثل العنصر الحالي في القائمة
...
}
يمكن أيضًا استخدام القائمة L مع كائن for:
for (Object o : L) {
// استخدام الكائن o
}
- 2 - تقوم الطريقة getSingleResult بتنفيذ أمر JPQL / SQL SELECT الذي يعرض كائنًا واحدًا.
- 3 - تُنفِّذ الطريقة executeUpdate أمر SQL للتحديث أو الحذف، وتُرجع عدد الصفوف التي تأثرت بالعملية.
- 4 - تسمح الطريقة setParameter(String, Object) بتعيين قيمة لمعلمة مسماة في أمر JPQL تم تعيين معلماته
- 5 - الطريقة setParameter(int, Object) ولكن المعلمة لا يتم تحديدها باسمها بل بموقعها في الأمر JPQL.
4.4. استعلامات JPQL
JPQL (لغة استعلامات استمرارية Java) هي لغة الاستعلامات الخاصة بطبقة JPA. لغة JPQL مشابهة للغة SQL الخاصة بقواعد البيانات. بينما تعمل SQL مع الجداول، تعمل JPQL مع كائنات الصور في هذه الجداول. سنقوم بدراسة مثال ضمن البنية التالية:
![]() |
قاعدة البيانات التي سنسميها [dbrdvmedecins2] هي قاعدة بيانات MySQL5 تحتوي على أربعة جداول:
![]() |
وهي تجمع معلومات تسمح بإدارة مواعيد مجموعة من الأطباء.
4.4.1. الجدول [MEDECINS]
تحتوي على معلومات عن الأطباء.
![]() | ![]() |
- ID: رقم تعريف الطبيب - المفتاح الأساسي للجدول
- VERSION: رقم تعريف إصدار السطر في الجدول. يتم زيادة هذا الرقم بمقدار 1 في كل مرة يتم فيها إجراء تعديل على السطر.
- NOM: اسم الطبيب
- PRENOM: اسمه الأول
- TITRE: لقبه (الآنسة، السيدة، السيد)
4.4.2. الجدول [CLIENTS]
يتم تسجيل عملاء الأطباء المختلفين في الجدول [CLIENTS]:
![]() | ![]() |
- ID: رقم تعريف العميل - المفتاح الأساسي للجدول
- VERSION: رقم تعريف إصدار السطر في الجدول. يتم زيادة هذا الرقم بمقدار 1 في كل مرة يتم فيها إجراء تعديل على السطر.
- NOM: اسم العميل
- PRENOM: اسمه الأول
- TITRE: لقبه (آنسة، سيدة، سيد)
4.4.3. الجدول [CRENEAUX]
تسرد هذه الجدول الفترات الزمنية التي يمكن فيها استخدام RV:
![]() |
![]() |
- ID: الرقم الذي يحدد الفترة الزمنية - المفتاح الأساسي للجدول (السطر 8)
- VERSION: الرقم الذي يحدد إصدار السطر في الجدول. يتم زيادة هذا الرقم بمقدار 1 في كل مرة يتم فيها إجراء تعديل على السطر.
- ID_MEDECIN: الرقم الذي يحدد الطبيب الذي ينتمي إليه هذا الموعد – مفتاح خارجي في العمود MEDECINS(ID).
- HDEBUT: وقت بدء الفترة الزمنية
- MDEBUT: دقائق بداية الفترة
- HFIN: ساعة انتهاء الفترة الزمنية
- MFIN: دقائق نهاية الفترة الزمنية
تشير السطر الثاني من الجدول [CRENEAUX] (انظر [1] أعلاه) ، على سبيل المثال ، إلى أن الفترة رقم 2 تبدأ في الساعة 8:20 وتنتهي في الساعة 8:40 وتخص الطبيب رقم 1 (السيدة ماري PELISSIER).
4.4.4. الجدول [RV]
تسرد هذه الجدولة المواعيد المحددة لكل طبيب:
![]() |
- ID: رقم يحدد RV بشكل فريد – مفتاح أساسي
- JOUR: يوم RV
- ID_CRENEAU: الفترة الزمنية لـ RV - مفتاح خارجي في الحقل [ID] من الجدول [CRENEAUX] – يحدد في آن واحد الفترة الزمنية والطبيب المعني.
- ID_CLIENT: رقم العميل الذي تم الحجز لصالحه – مفتاح خارجي في الحقل [ID] من الجدول [CLIENTS]
تحتوي هذه الجدولة على قاعدة بيانات ( ) تفرض التفرّد على قيم الأعمدة المرتبطة (JOUR، ID_CRENEAU):
إذا كان أحد صفوف الجدول [RV] يحتوي على القيمة (JOUR1، ID_CRENEAU1) للأعمدة (JOUR، ID_CRENEAU)، فلا يمكن أن توجد هذه القيمة في أي مكان آخر. وإلا، فهذا يعني أنه تم أخذ RV مرتين في نفس الوقت لنفس الطبيب. من منظور برمجة Java، يقوم برنامج التشغيل JDBC للقاعدة بإطلاق SQLException عند حدوث هذه الحالة.
السطر الذي يساوي 3 في id (انظر [1] أعلاه) يعني أنه تم حجز RV للفترة رقم 20 والعميل رقم 4 في 23/08/2006. يخبرنا الجدول [CRENEAUX] أن الموعد رقم 20 يتوافق مع الفترة الزمنية 16:20 - 16:40 وينتمي إلى الطبيب رقم 1 (السيدة ماري PELISSIER). تُظهر لنا الجدولة [CLIENTS] أن العميل رقم 4 هو الآنسة بريجيت BISTROU.
4.4.5. إنشاء قاعدة البيانات
لإنشاء الجداول وتعبئتها، يمكن استخدام البرنامج النصي [dbrdvmedecins2.sql]. باستخدام [WampServer]، يمكننا القيام بما يلي:
![]() |
- في [1]، انقر على أيقونة [WampServer] واختر الخيار [PhpMyAdmin] [2]،
- في [3]، في النافذة التي فتحت، حدد الرابط [Bases de données]،
![]() |
- إلى [2]، نقوم بإنشاء قاعدة بيانات أطلقنا عليها اسم [4] وترميز [5]،
- في [7]، تم إنشاء قاعدة البيانات. نضغط على الرابط الخاص بها،
![]() |
- في [8]، نستورد ملف SQL،
- الذي يتم تحديده في نظام الملفات باستخدام الزر [9]،
![]() |
- في [11]، نختار البرنامج النصي SQL وفي [12] نقوم بتنفيذه،
- في [13]، تم إنشاء الجداول الأربعة للقاعدة. نتبع أحد الروابط،
![]() |
- في [14]، محتوى الجدول.
بعد ذلك، لن نعود إلى هذه القاعدة مرة أخرى. لكن القارئ مدعو لمتابعة تطورها عبر البرامج، خاصةً عندما لا تعمل.
4.4.6. الطبقة [JPA]
لنعد إلى بنية المثال:
![]() |
نقوم الآن ببناء مشروع Maven للطبقة [JPA].
4.4.7. مشروع Netbeans
هو التالي:
![]() |
- في [1]، نقوم بإنشاء مشروع Maven من النوع [Java Application] [2]،
- في [3]، نسمي المشروع،
![]() |
- في [4]، المشروع الذي تم إنشاؤه.
4.4.8. إنشاء الطبقة [JPA]
لنعد إلى البنية التي يتعين علينا إنشاؤها:
![]() |
باستخدام Netbeans، يمكن إنشاء الطبقة [JPA] تلقائيًا. من المفيد معرفة طرق الإنشاء التلقائي هذه لأن الكود الذي تم إنشاؤه يقدم إرشادات قيّمة حول كيفية كتابة كيانات JPA.
4.4.9. إنشاء اتصال Netbeans بقاعدة البيانات
- تشغيل SGBD MySQL 5 حتى يتوفر BD،
- إنشاء اتصال Netbeans بقاعدة البيانات [dbrdvmedecins2]،
![]() |
- في علامة التبويب [Services] [1]، في الفرع [Databases] [2]، حدد برنامج التشغيل JDBC MySQL [3]،
- ثم حدد الخيار [4] "Connect Using" الذي يسمح بإنشاء اتصال بقاعدة بيانات MySQL،
- في [5]، أدخل المعلومات المطلوبة. في [6]، اسم قاعدة البيانات، وفي [7] اسم المستخدم وكلمة المرور لقاعدة البيانات،
- في [8]، يمكن اختبار المعلومات التي تم تقديمها،
- في [9]، الرسالة المتوقعة عندما تكون هذه المعلومات صحيحة،
![]() |
- في [10]، يتم إنشاء الاتصال. ونرى هنا الجداول الأربعة لقاعدة البيانات المتصلة.
4.4.10. إنشاء وحدة استمرارية
لنعد إلى البنية قيد الإنشاء:
![]() |
نحن بصدد بناء الطبقة [JPA]. يتم تكوينها في ملف [persistence.xml] حيث يتم تعريف وحدات الاستمرارية. تحتاج كل وحدة منها إلى المعلومات التالية:
- خصائص الوصول إلى قاعدة البيانات (URL، المستخدم، كلمة المرور)،
- الفئات التي ستكون صورًا لجداول قاعدة البيانات،
- التنفيذ JPA المستخدم. في الواقع، JPA هي مواصفة يتم تنفيذها بواسطة منتجات متنوعة. هنا، سنستخدم Hibernate.
يمكن لـ Netbeans إنشاء ملف الاستمرارية هذا باستخدام مساعد.
![]() |
- انقر بزر الماوس الأيمن على المشروع واختر إنشاء وحدة استمرارية [1]،
- في [2]، قم بإنشاء وحدة استمرارية،
![]() |
- في [3]، قم بتسمية وحدة الاستمرارية التي يتم إنشاؤها،
- في [4]، اختر تطبيق Hibernate JPA (JPA 2.0)،
- في [5]، حدد أن جداول BD قد تم إنشاؤها بالفعل وبالتالي لن يتم إنشاؤها. قم بتأكيد المساعد،
- في [6]، المشروع الجديد،
- في [7]، تم إنشاء الملف [persistence.xml] في المجلد [META-INF]،
- في [8]، تمت إضافة تبعيات جديدة إلى مشروع Maven.
الملف [META-INF/persistence.xml] الذي تم إنشاؤه هو التالي:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
</properties>
</persistence-unit>
</persistence>
وهو يتضمن المعلومات التي تم إدخالها في المساعد:
- السطر 3: اسم وحدة الاستمرارية،
- السطر 3: نوع المعاملات مع قاعدة البيانات. هنا، يشير RESOURCE_LOCAL إلى أن التطبيق سيدير معاملاته بنفسه،
- الأسطر 6-9: خصائص JDBC لمصدر البيانات.
في علامة التبويب [Design]، يمكن الحصول على عرض عام للملف [persistence.xml]:
![]() |
للحصول على سجلات Hibernate، نكمل الملف [persistence.xml] بالطريقة التالية:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
- السطر 11: نطلب عرض الأوامر SQL الصادرة عن Hibernate،
- السطر 12: تتيح هذه الخاصية عرضها بتنسيق محدد.
تمت إضافة تبعيات إلى المشروع. الملف [pom.xml] هو التالي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-rdvmedecins-jpql-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-rdvmedecins-jpql-hibernate</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.1_spec</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.15.0-GA</version>
</dependency>
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.1.Final</version>
</dependency>
</dependencies>
</project>
تتعلق جميع التبعيات المضافة بـ Hibernate ORM. سنضيف تبعية برنامج التشغيل JDBC من MySQL:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
4.4.11. إنشاء الكيانات JPA
يمكن إنشاء الكيانات JPA بواسطة مساعد Netbeans:
![]() |
- في [1]، يتم إنشاء كيانات JPA من قاعدة بيانات،
![]() |
- إلى [2]، يتم تحديد الاتصال [dbrdvmedecins2] الذي تم إنشاؤه مسبقًا،
- في [3]، يتم تحديد جميع الجداول في قاعدة البيانات المرتبطة،
![]() |
- في [4]، نسمي فئات Java المرتبطة بالجداول الأربعة،
- وكذلك اسم حزمة [5]،
- في [6]، تقوم JPA بتجميع صفوف الجداول من BD في مجموعات. نختار القائمة كمجموعة،
![]() |
- في [7]، فئات Java التي أنشأها المساعد.
4.4.12. الكيانات JPA التي تم إنشاؤها
الكيان [Medecin] هو صورة الجدول [medecins]. فئة Java مليئة بالتعليقات التوضيحية التي تجعل الكود صعب القراءة للوهلة الأولى. إذا احتفظنا فقط بما هو ضروري لفهم دور الكيان، نحصل على الكود التالي:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "medecins")
public class Medecin implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
// منشئات
....
// مُستردات ومُعيّنات
....
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- السطر 4، التعليق التوضيحي @Entity يجعل من الفئة [Medecin] كيانًا JPA، c.a.d. فئة مرتبطة بجدول BD عبر API JPA،
- السطر 5، اسم الجدول BD المرتبط بالكيان JPA. كل حقل في الجدول يمثل حقلًا في فئة Java،
- السطر 6، الفئة تنفذ واجهة Serializable. وهذا ضروري في تطبيقات العميل/الخادم، حيث يتم تسلسل الكيانات بين العميل والخادم.
- السطران 10-11: الحقل id في الفئة [Medecin] يتوافق مع الحقل [ID] (السطر 10) في الجدول [medecins]،
- السطران 13-14: الحقل titre في الفئة [Medecin] يتطابق مع الحقل [TITRE] (السطر 13) في الجدول [medecins]،
- السطران 16-17: الحقل "الاسم" للفئة [Medecin] يطابق الحقل [NOM] (السطر 16) في الجدول [medecins]،
- السطران 19-20: الحقل version للفئة [Medecin] يتطابق مع الحقل [VERSION] (السطر 19) في الجدول [medecins]. هنا، لا يتعرف المساعد على حقيقة أن العمود هو في الواقع عمود إصدار يجب زيادته عند كل تعديل للسطر الذي ينتمي إليه. لإعطائه هذا الدور، يجب إضافة التعليق التوضيحي @Version. سنقوم بذلك في خطوة لاحقة،
- السطران 22-23: الحقل prenom في الفئة [Medecin] يتوافق مع الحقل [PRENOM] في الجدول [medecins]،
- السطران 10-11: الحقل id يطابق المفتاح الأساسي [ID] للجدول. توضح التعليقات التوضيحية في السطرين 8-9 هذه النقطة،
- السطر 8: تشير التعليقات التوضيحية @Id إلى أن الحقل المعلق عليه مرتبط بالمفتاح الأساسي للجدول،
- السطر 9: ستقوم الطبقة [JPA] بإنشاء المفتاح الأساسي للسطور التي ستقوم بإدراجها في الجدول [Medecins]. هناك عدة استراتيجيات ممكنة. هنا تشير الاستراتيجية GenerationType.IDENTITY إلى أن الطبقة JPA ستستخدم الوضع auto_increment للجدول MySQL،
- السطران 25-26: تحتوي الجدول [creneaux] على مفتاح خارجي في الجدول [medecins]. تنتمي كل فترة زمنية إلى طبيب واحد. وبالعكس، يرتبط كل طبيب بعدة فترات زمنية. لذلك لدينا علاقة واحد (طبيب) إلى عدة (فترات زمنية)، وهي علاقة محددة بالتعليق @OneToMany بواسطة JPA (السطر 25). سيحتوي حقل السطر 26 على جميع المواعيد المخصصة للطبيب. وذلك دون الحاجة إلى برمجة. لفهم السطر 25 بشكل كامل، يتعين علينا عرض الفئة [Creneau].
وهي كما يلي:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "MDEBUT")
private int mdebut;
@Column(name = "HFIN")
private int hfin;
@Column(name = "HDEBUT")
private int hdebut;
@Column(name = "MFIN")
private int mfin;
@Column(name = "VERSION")
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
private List<Rv> rvList;
// منشئات
...
// الوصول والتعيين
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
نكتفي بالتعليق على التعليقات التوضيحية الجديدة:
- لقد ذكرنا أن الجدول [creneaux] يحتوي على مفتاح خارجي إلى الجدول [medecins]: حيث يرتبط موعد طبيب واحد. ويمكن أن ترتبط عدة مواعيد بنفس الطبيب. لدينا علاقة من الجدول [creneaux] إلى الجدول [medecins] والتي توصف بأنها علاقة متعددة (فترات) إلى واحد (طبيب). التعليق @ManyToOne في السطر 32 هو الذي يُستخدم لتعريف المفتاح الأجنبي،
- السطر 31 مع التعليق @JoinColumn يحدد علاقة المفتاح الأجنبي: العمود [ID_MEDECIN] في الجدول [creneaux] هو مفتاح خارجي في العمود [ID] في الجدول [medecins]،
- السطر 33: إشارة إلى الطبيب المالك للفتحة الزمنية. يتم الحصول عليها هنا أيضًا دون الحاجة إلى البرمجة.
وبالتالي، فإن رابط المفتاح الأجنبي بين الكيان [Creneau] والكيان [Medecin] يتجسد في شكل تعليقين:
- في الكيان [Creneau]:
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
- في الكيان [Medecin]:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
يعكس التعليقان نفس العلاقة: علاقة المفتاح الأجنبي للجدول [creneaux] بالجدول [medecins]. ويقال إنهما متعاكسان. العلاقة @ManyToOne هي الوحيدة الضرورية. فهي تحدد علاقة المفتاح الأجنبي بشكل لا لبس فيه. أما العلاقة @OneToMany فهي اختيارية. وإذا كانت موجودة، فإنها تكتفي بالإشارة إلى العلاقة @ManyToOne المرتبطة بها. هذا هو معنى السمة mappedBy في السطر 1 من الكيان [Medecin]. قيمة هذا السمة هي اسم حقل الكيان [Creneau] الذي يحتوي على التعليق التوضيحي @ManyToOne الذي يحدد المفتاح الأجنبي. وفي نفس السطر 1 من الكيان [Medecin]، يحدد السمة cascade=CascadeType.ALL سلوك الكيان [Medecin] تجاه الكيان [Creneau]:
- إذا تم إدراج كيان جديد [Medecin] في قاعدة البيانات، فيجب إدراج الكيانات [Creneau] في حقل السطر 2 أيضًا،
- إذا تم تعديل كيان [Medecin] في قاعدة البيانات، فيجب تعديل كيانات [Creneau] في حقل السطر 2 أيضًا،
- إذا تم حذف كيان [Medecin] من قاعدة البيانات، فيجب حذف كيانات [Creneau] من حقل السطر 2 أيضًا.
نقدم رمز الكيانين الآخرين دون تعليقات خاصة لأنهما لا يقدمان أي ترميزات جديدة.
الكيان [Client]
package rdvmedecins.jpa;
...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
private List<Rv> rvList;
// منشئات
...
// الوصول والتعيين
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- تعكس السطران 24-25 علاقة المفتاح الخارجي بين الجدول [rv] والجدول [clients].
الكيان [Rv]:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau idCreneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client idClient;
// منشئات
...
// الوصول والتعيين
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- السطر 13 يحدد الحقل "اليوم" من نوع Java Date. ويشار إلى أنه في الجدول [rv]، العمود [JOUR] (السطر 12) هو من نوع التاريخ (بدون ساعة)،
- الأسطر 16-18: تحدد علاقة المفتاح الأجنبي التي تربط الجدول [rv] بالجدول [creneaux]،
- السطور 20-22: تحدد علاقة المفتاح الأجنبي التي تربط الجدول [rv] بالجدول [clients].
يتيح لنا الإنشاء التلقائي للكيانات JPA الحصول على قاعدة عمل. أحيانًا تكون كافية، وأحيانًا لا. وهذا هو الحال هنا:
- يجب إضافة التعليق التوضيحي @Version إلى الحقول المختلفة الخاصة بالإصدار في الكيانات،
- يجب كتابة طرق toString أكثر وضوحًا من تلك التي تم إنشاؤها،
- الكيانات [Medecin] و [Client] متشابهة. سنجعلها مشتقة من فئة [Personne]،
- سنقوم بإزالة العلاقات @OneToMany العكسية للعلاقات @ManyToOne. فهي ليست ضرورية وتسبب تعقيدات في البرمجة،
- ونحذف التحقق من صحة @NotNull على المفاتيح الأساسية. عندما نحفظ كيان JPA مع MySQL، يكون للكيان في البداية مفتاح أساسي null. ولا يكون للمفتاح الأساسي للعنصر المحفوظ قيمة إلا بعد حفظه في قاعدة البيانات.
مع هذه المواصفات، تصبح الفئات المختلفة كما يلي:
تُستخدم فئة Personne لتمثيل الأطباء والعملاء:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@MappedSuperclass
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "TITRE")
private String titre;
@Basic(optional = false)
@Column(name = "NOM")
private String nom;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@Basic(optional = false)
@Column(name = "PRENOM")
private String prenom;
// منشئات
...
// الوصول والتعيين
...
@Override
public String toString() {
return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
}
}
- السطر 6: تجدر الإشارة إلى أن الفئة [Personne] ليست كيانًا (@Entity) بحد ذاتها. ستكون الفئة الأم للكيانات. يشير التعليق التوضيحي @MappedSuperClass إلى هذه الحالة.
تغلف الكيان [Client] صفوف الجدول [clients]. وهي مشتقة من الفئة [Personne] السابقة:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "clients")
public class Client extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// منشئات
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
- السطر 6: الفئة [Client] هي كيان Jpa،
- السطر 7: وهي مرتبطة بالجدول [clients]،
- السطر 8: وهي مشتقة من الفئة [Personne].
الكيان [Medecin] الذي يغلف أسطر الجدول [medecins] يتبع نفس النموذج:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// منشئات
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Médecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
تغلف الكيان [Creneau] صفوف الجدول [creneaux]:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "MDEBUT")
private int mdebut;
@Basic(optional = false)
@Column(name = "HFIN")
private int hfin;
@Basic(optional = false)
@NotNull
@Column(name = "HDEBUT")
private int hdebut;
@Basic(optional = false)
@Column(name = "MFIN")
private int mfin;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin medecin;
// منشئات
...
// مُستردات ومُعيّنات
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
// TODO: تحذير - لن تعمل هذه الطريقة في حالة عدم تعيين حقول المعرف
...
}
@Override
public String toString() {
return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
}
}
- السطور 40-42 تمثل العلاقة "متعدد إلى واحد" الموجودة بين الجدول [creneaux] والجدول [medecins] في قاعدة البيانات: الطبيب لديه عدة مواعيد، والموعد ينتمي إلى طبيب واحد.
تغلف الكيان [Rv] الأسطر في الجدول [rv]:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau creneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client client;
// منشئات
...
// مُستردات ومُعيّنات
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Rv[%s, %s, %s]", id, creneau, client);
}
}
- السطور 27-29 تمثل العلاقة "متعدد إلى واحد" الموجودة بين الجدول [rv] والجدول [clients] (يمكن أن يظهر عميل واحد في عدة Rv) في قاعدة البيانات، بينما تمثل الأسطر 23-25 العلاقة "متعدد إلى واحد" الموجودة بين الجدول [rv] والجدول [creneaux] (يمكن أن يظهر موعد واحد في عدة Rv).
4.4.13. رمز الوصول إلى البيانات
سنقوم الآن بإضافة رمز الوصول إلى البيانات إلى المشروع عبر الطبقة JPA:
![]() |
![]() |
الفئة [MainJpql] هي كما يلي:
package rdvmedecins.console;
import java.util.Scanner;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class MainJpql {
public static void main(String[] args) {
// EntityManagerFactory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-rdvmedecins-jpql-hibernatePU");
// entityManager
EntityManager em = emf.createEntityManager();
// ماسح لوحة المفاتيح
Scanner clavier = new Scanner(System.in);
// حلقة إدخال الاستعلامات JPQL
System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
String requete = clavier.nextLine();
while (!requete.trim().equals("*")) {
try {
// عرض نتيجة الاستعلام
for (Object o : em.createQuery(requete).getResultList()) {
System.out.println(o);
}
} catch (Exception e) {
System.out.println("L'exception suivante s'est produite : " + e);
}
// إفراغ سياق الاستمرارية
em.clear();
// استعلام جديد
System.out.println("---------------------------------------------");
System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
requete = clavier.nextLine();
}
// إغلاق الموارد
em.close();
emf.close();
}
}
- السطر 12: إنشاء EntityManagerFactory المرتبط بوحدة الاستمرارية التي أنشأناها سابقًا. معلمة الطريقة createEntityManagerFactory هي اسم وحدة الاستمرارية هذه:
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- السطر 14: إنشاء EntityManager الذي يدير طبقة الاستمرارية،
- السطر 19: إدخال استعلام JPQL select،
- الأسطر 23-28: عرض نتيجة الاستعلام،
- السطر 20: يتوقف الإدخال عندما يكتب المستخدم *.
السؤال: اذكر الاستعلامات JPQL التي تسمح بالحصول على المعلومات التالية:
- قائمة الأطباء مرتبة حسب ترتيب أسمائهم التنازلي
- قائمة الأطباء الذين لقبهم = 'Mr'
- قائمة المواعيد المتاحة للسيدة بيليسييه
- قائمة المواعيد المحددة مرتبة تصاعديًا حسب الأيام
- قائمة العملاء (الاسم) الذين حجزوا موعدًا مع السيدة PELISSIER في 24/08/2006
- عدد عملاء السيدة PELISSIER في 24/08/2006
- العملاء الذين لم يحجزوا موعدًا
- الأطباء الذين ليس لديهم مواعيد
سنستلهم من المثال الوارد في الفقرة 2.7 من [ref1]. فيما يلي مثال على التنفيذ:
- السطر 2: الاستعلام JPQL،
- الأسطر 3-11: الاستعلام SQL المقابل،
- الأسطر 12-15: نتيجة الاستعلام JPQL.
4.5. الروابط بين سياق الاستمرارية و SGBD
4.5.1. فئة Personne
4.5.2. برنامج الاختبار
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
4.5.3. تكوين Hibernate
4.5.4. تكوين log4j.properties
4.5.5. النتائج
السؤال: اربط بين كود Java والنتائج المعروضة.





































