Skip to content

7. Spring Data JPA EclipseLink

7.1. مقدمة

نحن نعيد استخدام البنية السابقة، التي نقوم الآن بتنفيذها باستخدام طبقة JPA/EclipseLink.

7.2. إعداد بيئة التطوير

باستخدام STS، قم بتنزيل مشروع [myql-config-jpa-hibernate] [1-4]:

ثم قم باستيراد مشروع [mysl-config-jpa-eclipselink] [5] الموجود في المجلد [<examples>/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>
        <!-- dépendances variables ********************************************** -->
        <!-- JPA provider -->
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.6.0</version>
        </dependency>
        <!-- dépendances constantes ********************************************** -->
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- configuration JDBC inherited -->
        <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>
            <!-- This plugin ensures the EclipseLink static weaving -->
            <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>
                <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. -->
                <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 لنظام إدارة قواعد البيانات المستخدم وتفاصيل الاتصال بقاعدة البيانات؛
  • الأسطر 35–40: مشروع تكوين طبقة JDBC يتضمن مكتبة [Spring JDBC]، والتي تم استبدالها هنا بمكتبة [Spring Data JPA]. لذلك، نحدد عدم تضمينها في تبعيات المشروع. ومع ذلك، إذا بقيت، فإن هذا لا يسبب أي أخطاء؛
  • يقوم المكون الإضافي في الأسطر 58–81 بتنفيذ نسج كيانات JPA. ما يُعرف بالنسج هو تحويل (إثراء) كيانات JPA بحيث تدعم التحميل المتأخر. لم نكن بحاجة إلى تكوين Hibernate لكي يحدث هذا النسج. بالنسبة لـ 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 بشكل افتراضي. يلزم إجراء تكوين خاص لكي يقوم بذلك. بعد بحث طويل لم يسفر عن شيء، وجدت الحل في الرابط المذكور في السطر 51. عند تضمين الأسطر 58-81 في ملف [pom.xml]، يبلغ Eclipse عن وجود خطأ في الملف. هذه مشكلة تكوين تتعلق بالمكوّن الإضافي [m2e]، الذي يتعامل مع مشاريع Maven داخل Eclipse. تحتاج إلى إضافة الأسطر 83-119 لحل الخطأ.

في النهاية، تكون التبعيات كما يلي:

  

7.3.2. تكوين الربيع

 

تقوم فئة [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 {
 
    // the provider JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        // Note: JPA entities and Eclipselink configuration are in the META-INF/persistence.xml file
        EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        eclipseLinkJpaVendorAdapter.setShowSql(false);
        eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
        eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
        return eclipseLinkJpaVendorAdapter;
    }
 
    // data source
    @Bean
    public DataSource dataSource() {
        // data source TomcatJdbc
        DataSource dataSource = new DataSource();
        // configuration access JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
        // initially open connections
        dataSource.setInitialSize(5);
        // result
        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;
    }
 
    // Transaction manager
    @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، كان الكود كما يلي:
factory.setPackagesToScan(ENTITIES_PACKAGES);

والذي كان يُستخدم لتحديد مكان البحث عن كيانات JPA. هنا، نعتمد على ملف [persistence.xml] (تعليق في السطر 25) (انظر القسم 6.3.4) من أجل:

  • تحديد كيانات JPA؛
  • تكوين EclipseLink لدمج هذه الكيانات؛

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">
        <!-- entities 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>
        <!-- properties required for [@ManyToOne] to be searched in LAZY mode -->
        <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 للربط الثابت. هناك نوعان من الربط:
    • [ثابت]: يتم نسج كيانات JPA بمجرد إنشاء مثيل لطبقة JPA؛
    • [dynamic]: يتم إثراء (نسج) كيانات 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. فهو يضيف سمة SQL [ON DELETE CASCADE] إلى المفاتيح الخارجية (هنا PRODUCTS[CATEGORY_ID] ---> CATEGORIES[ID])، مما يضمن أنه عند حذف أي فئة من جدول [CATEGORIES]، يتم أيضًا حذف المنتجات المقابلة لها في جدول [PRODUCTS

ملاحظة: من المهم ملاحظة أن هذا التعليق التوضيحي يُستخدم عند إنشاء الجدول، كما رأينا للتو، وعند الاستعلام عنه. يفترض EclipseLink أن سمة SQL [ON DELETE CASCADE] موجودة ويستخدمها كلما طُلب منه حذف فئة. وقد يؤدي غيابها إلى حدوث أخطاء.

7.6. طبقة الاختبار

  

الاختبارات المذكورة أعلاه مطابقة لتلك الخاصة بتنفيذات Spring JDBC و Spring JPA Hibernate. راجع الصفحات التالية إذا لزم الأمر:

النتائج التي تم الحصول عليها هي كما يلي:

  • في [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]

نرى هنا أنه عند الوصول إلى حقل [Category.products] لفئة من نوع PROXY وحقل [Product.category] لمنتج من نوع PROXY، ننجح في استرداد المعلومات في كلتا الحالتين (السطران 7 و17). من بين تطبيقات JPA الثلاثة، هذا هو الوحيد الذي يسمح بذلك على كيانات PROXY.