Skip to content

7. Spring Data JPA EclipseLink

7.1. Introduction

ما معماری قبلی را مجدداً استفاده می‌کنیم که اکنون آن را با لایه JPA / EclipseLink پیاده‌سازی می‌کنیم.

7.2. راه‌اندازی محیط کاری

با استفاده از STS، پروژه [myql-config-jpa-hibernate] و [1-4] را از حالت فشرده خارج کنید:

سپس پروژه [mysl-config-jpa-eclipselink] [5] را که در پوشه [<exemples>/spring-database-config/mysql/eclipse] [6] قرار دارد، وارد کنید:

پس از انجام این کار، محیط Maven را برای تمام پروژه‌ها در [Package Explorer] ریست کنید (Alt-F5):

 

سپس، برای بررسی محیط کاری، پیکربندی ساخت با نام [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>
            <!-- QZXW2HTMLBW2h0dHBzOi8vZmxleGd1c2Uud29yZHByZXNzLmNvbS8yMDEzLzA4LzEwL21hdmVuLXNwcmluZy1kYXRhLWpwYS1lY2xpcHNlbGluay-hbmQtc3RhdGljLXdlYXZpbmcvXQZQX -->
            <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>
  • خطوط ۵–۷: آرتیفکت Maven تولید شده توسط این پروژه. این همان آرتیفکت پروژه [mysql-config-jpa-hibernate] است. این بدان معناست که در هر زمان تنها یکی از این پروژه‌ها می‌تواند فعال باشد؛
  • خطوط ۱۰–۱۴: پروژهٔ والد Maven، که نسخه‌های اکثر وابستگی‌های مورد نیاز پروژه را مشخص می‌کند؛
  • خطوط ۱۹–۲۲: کتابخانه EclipseLink؛
  • خطوط 26–29: کتابخانه Spring Data؛
  • خطوط ۳۲–۳۴: پروژه پیکربندی لایه JPA به پروژه پیکربندی لایه JDBC متکی است، که در میان موارد دیگر، درایور JDBC برای SGBD در حال استفاده و جزئیات پایگاه داده مورد استفاده را شامل می‌شود؛
  • خطوط ۳۵–۴۰: پروژه پیکربندی لایه JDBC شامل کتابخانه [Spring JDBC] است که در اینجا با کتابخانه [Spring Data JPA] جایگزین می‌شود. بنابراین توصیه می‌شود که در وابستگی‌های پروژه گنجانده نشود. با این حال، اگر باقی بماند، این امر باعث بروز هیچ خطایی نخواهد شد؛
  • پلاگین در خطوط 58–81، «weaving» را از میان موجودیت‌های JPA پیاده‌سازی می‌کند. آنچه در انگلیسی با نام «weaving» شناخته می‌شود، تبدیل (غنی‌سازی) اشیاء JPA است تا از بارگذاری تنبل (lazy loading) پشتیبانی کنند. برای رخ دادن این weaving نیازی به پیکربندی Hibernate نبود. برای EclipseLink، یک افزونه Maven لازم است. من زمان بسیار زیادی را صرف کردم تا بفهمم چگونه EclipseLink را مجبور کنم تا با ویژگی [fetch = FetchType.LAZY] از anotation [@ManyToOne] که در زیر نشان داده شده است، مطابقت داشته باشد:

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = ConfigJdbc.TAB_PRODUITS_CATEGORIE_ID)
private Categorie categorie;

مقررات JPA بیان می‌کند که ویژگی [fetch = FetchType.LAZY] از حاشیه نویسی [@ManyToOne] یک «اشاره» (hint) است (پیشنهاد) که پیاده‌سازی JPA ملزم به رعایت آن نیست. و در واقع، EclipseLink به‌طور پیش‌فرض آن را رعایت نمی‌کند. برای این کار نیاز به پیکربندی ویژه‌ای است. پس از جستجوهای بی‌نتیجه فراوان، راه‌حل مشکل URL را که در خط ۵۱ ذکر شده بود، پیدا کردم. وقتی خطوط ۵۸–۸۱ در فایل [pom.xml] گنجانده می‌شوند، اِکلیپس خطایی را در آن فایل گزارش می‌کند. این یک مشکل پیکربندی در پلاگین [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 است. ما فقط به تفاوت‌ها می‌پردازیم:

  • خطوط ۲۳–۳۱: بیون [jpaVendorAdapter] اکنون با استفاده از EclipseLink پیاده‌سازی شده است؛
  • خطوط 50–58: در نسخه JPA از Hibernate، کد به شرح زیر بود:
factory.setPackagesToScan(ENTITIES_PACKAGES);

که برای مشخص کردن محل یافتن انتیت‌های JPA استفاده می‌شد. در اینجا، ما به فایل [persistence.xml] (توضیح در خط ۲۵) (بخش ۶.۳.۴ را ببینید) ارجاع می‌دهیم تا:

  • تعریف موجودیت‌های 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>
  • خط ۴: واحد پایداری. می‌توان هر نامی را به آن داد (ویژگی name);
  • خطوط ۶–۱۰: پنج موجودیت JPA که باید مدیریت شوند؛
  • خط ۱۱ مهم است. گاهی اوقات یک پروژه، انتیت‌هایی را تعریف می‌کند که در زمینه‌های مختلف استفاده می‌شوند. خط ۱۱ تضمین می‌کند که هیچ انتیتی دیگری به غیر از آن‌هایی که در خطوط ۵–۱۰ تعریف شده‌اند، استفاده نخواهد شد. این امر زمانی مهم است که از این انتیت‌ها برای تولید جداول منبع داده استفاده می‌شود. وجود انتیت‌های اضافی منجر به تولید جداول اضافی می‌شود؛
  • خطوط ۱۳–۱۷: پیکربندی EclipseLink برای یک weaving ایستا. دو نوع weaving وجود دارد:
    • [statique]: اشیاء JPA به محض اینکه لایه JPA نمونه برداری می‌شود، بافته (enriched) می‌شوند؛
    • [dynamique]: موجودات 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]، که تضمین می‌کند هرگاه یک دسته‌بندی از جدول [CATEGORIES] حذف شود، محصولات متناظر در جدول [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 s
  • در [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، اطلاعات در هر دو مورد (خطوط ۷ و ۱۷) با موفقیت بازیابی می‌شود. از میان سه پیاده‌سازی JPA، این تنها پیاده‌سازی است که این امکان را برای اشیاء PROXY فراهم می‌کند.