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 (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 було написано:
factory.setPackagesToScan(ENTITIES_PACKAGES);

який використовувався для визначення місця пошуку об’єктів 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]: сутності JPA доповнюються (woven) одразу після інстанціювання шару JPA;
    • [dynamique]: сутності JPA збагачуються (woven) під час їхнього першого надходження до шару 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 с
  • у [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.