Skip to content

7. Spring Data JPA EclipseLink

7.1. Introduction

Wracamy do poprzedniej architektury, którą teraz wdrażamy z warstwą JPA / EclipseLink.

7.2. Konfiguracja środowiska pracy

Za pomocą pliku STS pobierz projekt [myql-config-jpa-hibernate] [1-4]:

a następnie zaimportuj projekt [mysl-config-jpa-eclipselink] [5] znajdujący się w folderze [<exemples>/spring-database-config/mysql/eclipse] [6]:

Następnie zresetuj środowisko Maven (Alt-F5) dla wszystkich projektów znajdujących się w folderze [Package Explorer]:

 

Następnie, aby sprawdzić środowisko robocze, uruchom konfigurację uruchomieniową o nazwie [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink]:

 

Ta konfiguracja uruchamia test o nazwie [JUnitTestDao]. Test ten musi zakończyć się powodzeniem:

 

7.3. Projekt konfiguracji warstwy JPA

  

Zadaniem tego projektu jest skonfigurowanie warstwy JPA w poniższej architekturze:

7.3.1. Konfiguracja Maven

Projekt jest projektem Maven i jest skonfigurowany za pomocą następującego pliku [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>
        <!-- zmienne zależności ********************************************** -->
        <!-- JPA dostawca -->
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.6.0</version>
        </dependency>
        <!-- stałe zależności ********************************************** -->
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- konfiguracja dziedziczona 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>
            <!-- Ta wtyczka zapewnia statyczne wplatanie 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>
                <!--Konfiguracja tej wtyczki służy wyłącznie do przechowywania ustawień Eclipse m2e. Nie ma ona żadnego wpływu na sam proces kompilacji w 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>
  • wiersze 5–7: artefakt Maven wygenerowany przez ten projekt. Jest to ten sam artefakt, co w projekcie [mysql-config-jpa-hibernate]. Oznacza to, że w danym momencie aktywny może być tylko jeden z tych projektów;
  • wiersze 10–14: nadrzędny projekt Maven, który określa wersje większości zależności niezbędnych dla projektu;
  • wiersze 19–22: biblioteka EclipseLink;
  • wiersze 26–29: biblioteka Spring Data;
  • wiersze 32–34: projekt konfiguracyjny warstwy JPA opiera się na projekcie konfiguracyjnym warstwy JDBC, który definiuje między innymi sterownik JDBC dla używanego SGBD oraz dane bazy danych, z której należy korzystać;
  • wiersze 35–40: projekt konfiguracji warstwy JDBC zawiera bibliotekę [Spring JDBC], która została tutaj zastąpiona biblioteką [Spring Data JPA]. W związku z tym zaleca się, aby nie uwzględniać jej w zależnościach projektu. Jeśli jednak pozostanie, nie spowoduje to żadnych błędów;
  • wtyczka z wierszy 58–81 implementuje „weaving” z elementów JPA. To, co w języku angielskim nazywa się „weaving”, to transformacja (wzbogacenie) encji JPA w celu zapewnienia obsługi funkcji Lazy Loading. Nie musieliśmy konfigurować Hibernate, aby ten weaving zadziałał. W przypadku EclipseLink potrzebna jest wtyczka Maven. Bardzo długo szukałem sposobu, aby wymusić na EclipseLink przestrzeganie atrybutu [fetch = FetchType.LAZY] z poniższej adnotacji [@ManyToOne]:

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

Specyfikacja JPA wskazuje, że atrybut [fetch = FetchType.LAZY] adnotacji [@ManyToOne] jest „wskazówką” (wskazówką), której implementacja JPA nie musi przestrzegać. I rzeczywiście, EclipseLink domyślnie jej nie przestrzega. Aby ją przestrzegała, konieczna jest specjalna konfiguracja. Po wielu bezowocnych poszukiwaniach znalazłem rozwiązanie dotyczące wtyczki URL, o której mowa w wierszu 51. Gdy dodamy wiersze 58–81 do pliku [pom.xml], Eclipse zgłasza błąd dotyczący tego pliku. Jest to problem związany z konfiguracją wtyczki [m2e], która odpowiada za zarządzanie projektami Maven w środowisku Eclipse. Aby usunąć ten błąd, należy dodać linie 83–119.

Ostatecznie zależności są następujące:

  

7.3.2. Konfiguracja Spring

 

Klasa [ConfigJpa] konfiguruje projekt 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 {

    // dostawca JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        // Uwaga: elementy JPA oraz konfiguracja EclipseLink znajdują się w pliku META-INF/persistence.xml
        EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        eclipseLinkJpaVendorAdapter.setShowSql(false);
        eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
        eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
        return eclipseLinkJpaVendorAdapter;
    }

    // źródło danych
    @Bean
    public DataSource dataSource() {
        // źródło danych TomcatJdbc
        DataSource dataSource = new DataSource();
        // konfiguracja dostępu JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
        // początkowo otwarte połączenia
        dataSource.setInitialSize(5);
        // wynik
        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;
    }

    // Menedżer transakcji
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

}

Konfiguracja ta jest analogiczna do tej opisanej w paragrafie 6.3.2, dotyczącej implementacji Hibernate o nazwie JPA. Przedstawiamy jedynie różnice:

  • wiersze 23–31: bean [jpaVendorAdapter] jest teraz zaimplementowany przy użyciu EclipseLink;
  • wiersze 50–58: w wersji Hibernate o numerze JPA zapisano:
factory.setPackagesToScan(ENTITIES_PACKAGES);

który służył do wskazania, gdzie należy szukać jednostek JPA. W tym przypadku odwołujemy się do pliku [persistence.xml] (komentarz w wierszu 25) (por. punkt 6.3.4) w celu:

  • zdefiniowanie elementów JPA;
  • skonfigurowania pliku EclipseLink dla pliku weaving tych jednostek;

7.4. Plik [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">
        <!-- podmioty 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>
        <!-- Właściwości niezbędne do wyszukiwania [@ManyToOne] w trybie 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>
  • wiersz 4: jednostka trwałości. Może ona nosić dowolną nazwę (atrybut name);
  • wiersze 6–10: pięć elementów JPA, które mają być zarządzane;
  • wiersz 11 jest ważny. Czasami zdarza się, że projekt definiuje encje wykorzystywane w różnych kontekstach. Wiersz 11 gwarantuje, że nie będzie żadnych innych encji poza tymi zdefiniowanymi w wierszach 5–10. Jest to istotne, gdy encje te są wykorzystywane do generowania tabel źródła danych. Nadmiar encji spowodowałby wygenerowanie zbędnych tabel;
  • wiersze 13–17: konfiguracja EclipseLink dla statycznego weaving. Istnieją dwa typy weaving:
    • [statique]: encje JPA są wzbogacane (woven) natychmiast po instancjonowaniu warstwy JPA;
    • [dynamique]: encje JPA są wzbogacane (woven) przy pierwszym pojawieniu się w warstwie JPA;

7.5. Entities JPA

  

Entytety JPA są opisane w paragrafie 6.3.3 dla implementacji Hibernate z dwiema różnicami:

  • wszystkie encje JPA posiadają adnotację [@Cache(alwaysRefresh = true)], która wyłącza buforowanie EclipseLink. W niniejszym dokumencie nie korzysta się z pamięci podręcznych stosowanych w implementacjach JPA. Pamięć podręczna EclipseLink wydaje się być domyślnie aktywna i powodowała błędy podczas testów.

@Entity
@Table(name = ConfigJdbc.TAB_CATEGORIES)
@JsonFilter("jsonFilterCategorie")
@Cache(alwaysRefresh = true)
public class Categorie implements AbstractCoreEntity {
  • wszystkie adnotacje [@OneToMany] są opatrzone adnotacją [@CascadeOnDelete]:

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "categorie", cascade = { CascadeType.ALL })
    @CascadeOnDelete
    private List<Produit> produits;

Adnotacja ta odgrywa rolę podczas generowania tabel na podstawie encji JPA. Dodaje ona do kluczy obcych (w tym przypadku PRODUITS[CATEGORIE_ID] ---> CATEGORIES[ID]) atrybut SQL [ON DELETE CASCADE], co powoduje, że za każdym razem, gdy usuwa się kategorię z tabeli [CATEGORIES], odpowiednie produkty w tabeli [PRODUITS] są również usuwane;

Uwaga: należy pamiętać, że ta adnotacja jest wykorzystywana zarówno podczas tworzenia tabeli, jak to właśnie widzieliśmy, jak i podczas jej eksploatacji. Tabela EclipseLink zakłada, że atrybut SQL [ON DELETE CASCADE] rzeczywiście istnieje i wykorzystuje go za każdym razem, gdy otrzymuje polecenie usunięcia kategorii. Jego brak spowodowałby błędy.

7.6. Warstwa testowa

  

Powyższe testy są identyczne z testami dla implementacji Spring JDBC i Spring JPA Hibernate. W razie potrzeby należy zapoznać się z następującymi stronami:

  • [JUnitTestCheckArguments]: punkt 4.11.1;
  • [JUnitTestDao]: punkt 4.11.2;
  • [JUnitTestPushTheLimits]: punkt 4.11.3;
  • [JUnitTestProxies]: punkt 6.4.5;

Uzyskane wyniki są następujące:

  • w [1], [JUnitTestPushTheLimits-EclipseLink]: 70,583 s
  • w [2], [JUnitTestPushTheLimits-Hibernate]: 78,945 s
  • w [3], [JUnitTestPushTheLimits-JDBC]: 36,09 s

Test [JUnitTestProxies] daje następujące wyniki na konsoli:

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]

Widać tutaj, że podczas uzyskiwania dostępu do pola [Categorie.produits] w kategorii typu PROXY oraz do pola [Produit.categorie] w produkcie typu PROXY, udaje się uzyskać informacje w obu przypadkach (wiersze 7 i 17). Spośród trzech implementacji JPA jest to jedyna, która umożliwia to w przypadku encji typu PROXY.