7. Spring Data JPA EclipseLink
7.1. مقدمة
نستأنف البنية السابقة التي نقوم الآن بتنفيذها باستخدام طبقة JPA / EclipseLink.
![]() |
7.2. إعداد بيئة العمل
باستخدام STS، قم بتنزيل المشروع [myql-config-jpa-hibernate] [1-4]:
![]() |
ثم قم باستيراد المشروع [mysl-config-jpa-eclipselink] [5] الموجود في المجلد [<exemples>/spring-database-config/mysql/eclipse] [6]:
![]() |
بعد ذلك، أعد ضبط بيئة Maven (Alt-F5) لجميع المشاريع الموجودة في [Package Explorer]:
![]() |
ثم، للتحقق من بيئة العمل، قم بتنفيذ التكوين المسمى [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink]:
![]() |
تقوم هذه التهيئة بتشغيل الاختبار [JUnitTestDao]. يجب أن ينجح هذا الاختبار:
![]() |
7.3. مشروع تكوين الطبقة JPA
![]() |
يهدف هذا المشروع إلى تكوين الطبقة JPA في البنية التالية:
![]() |
7.3.1. تكوين Maven
المشروع هو مشروع Maven ويتم تكوينه بواسطة الملف [pom.xml] التالي:
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>dvp.spring.database</groupId>
<artifactId>generic-config-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>configuration mysql openjpa</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- التبعيات المتغيرة ********************************************** -->
<!-- مزود JPA -->
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.0</version>
</dependency>
<!-- التبعيات الثابتة ********************************************** -->
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<!-- التكوين الموروث JDBC -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>generic-config-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<!-- [https://flexguse.wordpress.com/2013/08/10/maven-spring-data-jpa-eclipselink-and-static-weaving/] -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
<!-- يضمن هذا المكون الإضافي عملية النسج الثابت لـ EclipseLink -->
<plugin>
<artifactId>staticweave-maven-plugin</artifactId>
<groupId>de.empulse.eclipselink</groupId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>weave</goal>
</goals>
<phase>process-classes</phase>
<configuration>
<logLevel>ALL</logLevel>
<!-- <includeProjectClasspath>true</includeProjectClasspath> -->
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--تُستخدم إعدادات هذا المكون الإضافي لتخزين إعدادات Eclipse m2e فقط. ولا تؤثر على عملية البناء في Maven نفسها. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
de.empulse.eclipselink
</groupId>
<artifactId>
staticweave-maven-plugin
</artifactId>
<versionRange>
[1.0.0,)
</versionRange>
<goals>
<goal>weave</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnIncremental>true</runOnIncremental>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
- الأسطر 5-7: عنصر Maven الذي تم إنشاؤه بواسطة هذا المشروع. وهو نفس العنصر الخاص بمشروع [mysql-config-jpa-hibernate]. وهذا يعني أنه لا يمكن أن يكون سوى مشروع واحد من هذين المشروعين نشطًا في وقت واحد؛
- الأسطر 10-14: مشروع Maven الأصلي الذي يحدد إصدار معظم التبعيات اللازمة للمشروع؛
- الأسطر 19-22: مكتبة EclipseLink؛
- الأسطر 26-29: مكتبة Spring Data؛
- الأسطر 32-34: يعتمد مشروع تكوين الطبقة JPA على مشروع تكوين الطبقة JDBC الذي يحدد، من بين أمور أخرى، برنامج التشغيل JDBC الخاص بـ SGBD المستخدم وبيانات الاتصال بقاعدة البيانات المطلوب استخدامها؛
- الأسطر 35-40: يتضمن مشروع تكوين الطبقة JDBC المكتبة [Spring JDBC] التي تم استبدالها هنا بالمكتبة [Spring Data JPA]. لذلك يُنصح بعدم تضمينها في تبعيات المشروع. ومع ذلك، فإن بقاءها لن يتسبب في حدوث أخطاء؛
- يقوم المكون الإضافي في الأسطر 58-81 بتنفيذ الكيان «weaving» من كيانات JPA. ما يسميه الأنجلوساكسونيون بـ «weaving» هو تحويل (إثراء) كيانات «JPA» بحيث تدعم ميزة التحميل المتأخر (Lazy Loading). لم نحتج إلى تكوين Hibernate لتنفيذ هذا التغيير في كيان weaving. أما بالنسبة لـ EclipseLink، فيتطلب الأمر مكونًا إضافيًّا لـ Maven. لقد بحثتُ لفترة طويلة جدًّا عن طريقة لإجبار EclipseLink على الالتزام بالسمة [fetch = FetchType.LAZY] الخاصة بالتعليق التوضيحي [@ManyToOne] الموضح أدناه:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)
private Categorie categorie;
تشير المواصفة JPA إلى أن السمة [fetch = FetchType.LAZY] الخاصة بالتعليق التوضيحي [@ManyToOne] هي «تلميح» (اقتراح) لا يُلزم التنفيذ JPA بالالتزام به. وبالفعل، لا يلتزم EclipseLink به بشكل افتراضي. يلزم إجراء تكوين خاص حتى يلتزم به. بعد الكثير من البحث الذي باء بالفشل، وجدتُ الحل لمشكلة URL المذكورة في السطر 51. عند تضمين الأسطر 58-81 في الملف [pom.xml]، يُظهر Eclipse خطأً في الملف. هذه مشكلة تتعلق بإعدادات المكون الإضافي [m2e] الذي يتولى إدارة مشاريع Maven داخل Eclipse. يجب إضافة الأسطر من 83 إلى 119 لإزالة الخطأ.
وفي النهاية، تكون التبعيات كما يلي:
![]() |
7.3.2. تكوين Spring
![]() |
تقوم الفئة [ConfigJpa] بتكوين مشروع Spring:
package generic.jpa.config;
import generic.jdbc.config.ConfigJdbc;
import javax.persistence.EntityManagerFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@Import({ ConfigJdbc.class })
public class ConfigJpa {
// المزود JPA
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
// ملاحظة: الكيانات JPA وتكوين EclipseLink موجودة في الملف META-INF/persistence.xml
EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
eclipseLinkJpaVendorAdapter.setShowSql(false);
eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
return eclipseLinkJpaVendorAdapter;
}
// مصدر البيانات
@Bean
public DataSource dataSource() {
// مصدر البيانات TomcatJdbc
DataSource dataSource = new DataSource();
// إعدادات الوصول JDBC
dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
// الاتصالات المفتوحة في البداية
dataSource.setInitialSize(5);
// النتيجة
return dataSource;
}
// EntityManagerFactory
@Bean
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(jpaVendorAdapter);
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
EntityManagerFactory entityManagerFactory = factory.getObject();
return entityManagerFactory;
}
// مدير المعاملات
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
هذا التكوين مشابه للتكوين الموضح بالتفصيل في الفقرة 6.3.2، الخاص بتنفيذ Hibernate JPA. سنكتفي هنا بتوضيح الاختلافات فقط:
- الأسطر 23-31: يتم الآن تنفيذ المكون [jpaVendorAdapter] باستخدام EclipseLink؛
- الأسطر 50-58: في إصدار Hibernate JPA، كان مكتوبًا:
والذي كان يُستخدم لتحديد المكان الذي يجب البحث فيه عن الكيانات JPA. هنا، يتم الاعتماد على الملف [persistence.xml] (تعليق السطر 25) (انظر الفقرة 6.3.4) من أجل:
- تحديد الكيانات JPA؛
- تكوين EclipseLink لـ weaving لهذه الكيانات؛
7.4. الملف [persistence.xml]
![]() |
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
<persistence-unit name="generic-jpa-entities-dbproduitscategories" transaction-type="RESOURCE_LOCAL">
<!-- الكيانات JPA -->
<class>generic.jpa.entities.dbproduitscategories.Categorie</class>
<class>generic.jpa.entities.dbproduitscategories.Produit</class>
<class>generic.jpa.entities.dbproduitscategories.User</class>
<class>generic.jpa.entities.dbproduitscategories.Role</class>
<class>generic.jpa.entities.dbproduitscategories.UserRole</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<!-- الخصائص المطلوبة للبحث عن [@ManyToOne] في الوضع LAZY -->
<properties>
<property name="eclipselink.weaving" value="static" />
<property name="eclipselink.weaving.lazy" value="true" />
<property name="eclipselink.weaving.internal" value="true" />
</properties>
</persistence-unit>
</persistence>
- السطر 4: وحدة الاستمرارية. يمكن أن تحمل أي اسم (السمة name)؛
- الأسطر 6-10: الكيانات الخمسة JPA المطلوب إدارتها؛
- السطر 11 مهم. قد يحدث أحيانًا أن يحدد مشروع ما كيانات تُستخدم في أطر عمل مختلفة. يضمن السطر 11 عدم وجود كيانات أخرى غير تلك المحددة في الأسطر 5-10. وهذا أمر مهم عندما تُستخدم هذه الكيانات لإنشاء جداول مصدر البيانات. فوجود كيانات زائدة سيؤدي إلى إنشاء جداول زائدة؛
- السطور 13-17: تكوين EclipseLink لـ weaving ثابت. يوجد نوعان من weaving:
- [statique]: يتم دمج (woven) كيانات JPA بمجرد إنشاء مثيل للطبقة JPA؛
- [dynamique]: يتم إثراء (woven) كيانات JPA عند وصولها لأول مرة إلى الطبقة JPA؛
7.5. الكيانات JPA
![]() |
الكيانات JPA هي تلك الموصوفة في الفقرة 6.3.3 لتنفيذ Hibernate مع اختلافين:
- جميع الكيانات JPA تحمل التعليق التوضيحي [@Cache(alwaysRefresh = true)] الذي يلغي ذاكرة التخزين المؤقت لـ EclipseLink. في هذا المستند، لا يتم استخدام ذاكرات التخزين المؤقت الخاصة بتنفيذات JPA المستخدمة. ويبدو أن ذاكرة التخزين المؤقت الخاصة بـ EclipseLink نشطة بشكل افتراضي وكانت تتسبب في حدوث أخطاء في الاختبارات.
@Entity
@Table(name = ConfigJdbc.TAB_CATEGORIES)
@JsonFilter("jsonFilterCategorie")
@Cache(alwaysRefresh = true)
public class Categorie implements AbstractCoreEntity {
- جميع التعليقات التوضيحية [@OneToMany] مصحوبة بالتعليق التوضيحي [@CascadeOnDelete]:
@OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL })
@CascadeOnDelete
private List<Produit> produits;
يلعب هذا التعليق دورًا عند إنشاء الجداول من الكيانات JPA. وهي تضيف إلى المفاتيح الخارجية (هنا PRODUITS[CATEGORIE_ID] ---> CATEGORIES[ID]) السمة SQL [ON DELETE CASCADE]، مما يؤدي إلى حذف يتم حذف المنتجات المقابلة لها في الجدول [PRODUITS] أيضًا؛
ملاحظة: من المهم ملاحظة أن هذا التعليق التوضيحي يُستخدم سواء عند إنشاء الجدول كما رأينا للتو أو عند استخدامه. تفترض الجملة EclipseLink أن السمة SQL [ON DELETE CASCADE] موجودة بالفعل، وتستخدمها في كل مرة يُطلب منها حذف فئة. وغيابها سيؤدي إلى حدوث أخطاء.
7.6. طبقة الاختبارات
![]() |
![]() |
الاختبارات المذكورة أعلاه مطابقة لتلك الموجودة في تطبيقات Spring JDBC و Spring JPA Hibernate. يمكن الرجوع إلى الصفحات التالية إذا لزم الأمر:
- [JUnitTestCheckArguments]: الفقرة 4.11.1؛
- [JUnitTestDao]: الفقرة 4.11.2؛
- [JUnitTestPushTheLimits]: الفقرة 4.11.3؛
- [JUnitTestProxies]: الفقرة 6.4.5؛
النتائج التي تم الحصول عليها هي كما يلي:
![]() |
![]() |
- في [1]، [JUnitTestPushTheLimits-EclipseLink]: 70,583 ثانية
- في [2]، [JUnitTestPushTheLimits-Hibernate]: 78,945 ثانية
- في [3]، [JUnitTestPushTheLimits-JDBC]: 36,09 ثانية
يعطي الاختبار [JUnitTestProxies] النتائج التالية على وحدة التحكم:
Vidage de la base de données --------------------------------
doNothing
Vidage de la base de données --------------------------------
getShortCategoriesByName1 --------------------------------
Catégorie de type : PROXY
Catégorie :
1
Vidage de la base de données --------------------------------
getLongCategoriesByName1 --------------------------------
Catégorie de type : POJO
Catégorie :
1
Vidage de la base de données --------------------------------
getShortProduitsByName1 --------------------------------
Produit de type : PROXY
Nom de la catégorie du produit :
categorie[0]
Vidage de la base de données --------------------------------
getLongProduitsByName1 --------------------------------
Produit de type : POJO
Nom de la catégorie du produit :
categorie[0]
ونلاحظ هنا أنه عند الوصول إلى الحقل [Categorie.produits] لفئة من النوع PROXY وإلى الحقل [Produit.categorie] لمنتج من النوع PROXY، نتمكن من الحصول على المعلومات في كلتا الحالتين (السطران 7 و17). ومن بين التطبيقات الثلاثة JPA، هذا هو التطبيق الوحيد الذي يسمح بذلك على الكيانات PROXY.















