Skip to content

7. Spring Data JPA EclipseLink

7.1. Introduction

Nous reprenons l'architecture précédente que nous implémentons maintenant avec une couche JPA / EclipseLink.

7.2. Mise en place de l'environnement de travail

Avec STS, déchargez le projet [myql-config-jpa-hibernate] [1-4] :

puis importez le projet [mysl-config-jpa-eclipselink] [5] qui se trouve dans le dossier [<exemples>/spring-database-config/mysql/eclipse] [6] :

Ceci fait, réinitialisez l'environnement Maven (Alt-F5) de tous les projets présents dans [Package Explorer] :

 

Puis, pour vérifier l'environnement de travail, exécutez la configuration d'exécution nommée [spring-jpa-generic-JUnitTestDao-hibernate-eclipselink] :

 

Cette configuration exécute le test [JUnitTestDao]. Ce test doit réussir :

 

7.3. Le projet de configuration de la couche JPA

  

Ce projet a pour rôle de configurer la couche JPA de l'architecture ci-dessous :

7.3.1. Configuration Maven

Le projet est un projet Maven est configuré par le fichier [pom.xml] suivant :


<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 héritée -->
        <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>
  • lignes 5-7 : l'artifact Maven généré par ce projet. C'est le même que celui du projet [mysql-config-jpa-hibernate]. Cela signifie qu'un seul de ces projets peut être actif à un moment donné ;
  • lignes 10-14 : le projet Maven parent qui fixe la version de la plupart des dépendances nécessaires au projet ;
  • lignes 19-22 : la bibliothèque EclipseLink ;
  • lignes 26-29 : la bibliothèque Spring Data ;
  • lignes 32-34 : le projet de configuration de la couche JPA s'appuie sur celui de configuration de la couche JDBC qui définit entre autres choses, le pilote JDBC du SGBD utilisé et les coordonnées de la base à utiliser ;
  • lignes 35-40 : le projet de configuration de la couche JDBC inclut la bibliothèque [Spring JDBC] qui est ici remplacée par la bibliothèque [Spring Data JPA]. Aussi indique-t-on de ne pas l'inclure dans les dépendances du projet. Si elle reste, cela ne cause cependant pas d'erreurs ;
  • le plugin des lignes 58-81 implémentent le 'weaving' des entités JPA. Ce que les anglo-saxons appellent le weaving est la transformation (l'enrichissement) des entités JPA afin qu'elles supportent le Lazy Loading. Nous n'avons pas eu besoin de congigurer Hibernate pour que ce weaving ait lieu. Pour EclipseLink, il faut un plugin Maven. J'ai cherché très longtemps comment forcer EclipseLink à respecter l'attribut [fetch = FetchType.LAZY] de l'annotation [@ManyToOne] ci-dessous :

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

La spécification JPA indique que l'attribut [fetch = FetchType.LAZY] de l'annotation [@ManyToOne] est un 'hint' (suggestion) que l'implémentation JPA n'est pas tenue de respecter. Et effectivement EclipseLink ne la respecte pas par défaut. Il faut une configuration spéciale pour qu'il la respecte. Après beaucoup de recherches infructueuses, j'ai trouvé la solution à l'URL citée ligne 51. Lorsqu'on inclut les lignes 58-81 dans le fichier [pom.xml], Eclipse déclare une erreur sur le fichier. C'est un problème de configuration du plugin [m2e] qui assure la gestion des projets Maven au sein d'Eclipse. Il faut rajouter les lignes 83-119 pour lever l'erreur.

Au final, les dépendances sont les suivantes :

  

7.3.2. Configuration Spring

 

La classe [ConfigJpa] configure le projet 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 {

    // le provider JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        // Note : les entités JPA et la configuration d'Eclipselink sont dans le fichier META-INF/persistence.xml
        EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
        eclipseLinkJpaVendorAdapter.setShowSql(false);
        eclipseLinkJpaVendorAdapter.setDatabase(Database.MYSQL);
        eclipseLinkJpaVendorAdapter.setGenerateDdl(true);
        return eclipseLinkJpaVendorAdapter;
    }

    // source de données
    @Bean
    public DataSource dataSource() {
        // source de données TomcatJdbc
        DataSource dataSource = new DataSource();
        // configuration accès JDBC
        dataSource.setDriverClassName(ConfigJdbc.DRIVER_CLASSNAME);
        dataSource.setUsername(ConfigJdbc.USER_DBPRODUITSCATEGORIES);
        dataSource.setPassword(ConfigJdbc.PASSWD_DBPRODUITSCATEGORIES);
        dataSource.setUrl(ConfigJdbc.URL_DBPRODUITSCATEGORIES);
        // connexions ouvertes initialement
        dataSource.setInitialSize(5);
        // résultat
        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;
    }

}

Cette configuration est analogue à celle détaillée au paragraphe 6.3.2, pour l'implémentation JPA Hibernate. Nous ne détaillons que les différences :

  • lignes 23-31 : le bean [jpaVendorAdapter] est désormais implémenté avec EclipseLink ;
  • lignes 50-58 : dans la version JPA Hibernate, il était écrit :
factory.setPackagesToScan(ENTITIES_PACKAGES);

qui servait à désigner où devaient être cherchées les entités JPA. Ici, on s'en remet au fichier [persistence.xml] (commentaire ligne 25) (cf paragraphe 6.3.4) pour à la fois :

  • définir les entités JPA ;
  • configurer EclipseLink pour le weaving de ces entités ;

7.4. Le fichier [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">
        <!-- entités 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>
        <!-- propriétés nécessaires pour que le [@ManyToOne] puisse être cherché en mode 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>
  • ligne 4 : l'unité de persistence. Elle peut porter un nom quelconque (attribut name) ;
  • lignes 6-10 : les cinq entités JPA à gérer ;
  • la ligne 11 est importante. Il arrive parfois qu'un projet définisse des entités utilisées dans des cadres différents. La ligne 11 assure qu'on n'aura pas d'autres entités que celles définies lignes 5-10. C'est important lorsque celles-ci sont utilisées pour générer les tables de la source de données. Avoir des entités en trop génèrerait des tables en trop ;
  • lignes 13-17 : configuration d'EclipseLink pour un weaving statique. Il existe deux types de weaving :
    • [statique] : les entités JPA sont enrichies (woven) dès que la couche JPA est instanciée ;
    • [dynamique] : les entités JPA sont enrichies (woven) la première fois qu'elles arrivent dans la couche JPA ;

7.5. Les entités JPA

  

Les entités JPA sont celles décrites au paragraphe 6.3.3 pour l'implémentation Hibernate avec deux différences :

  • toutes les entités JPA ont l'annotation [@Cache(alwaysRefresh = true)] qui annule le cache d'EclipseLink. Dans ce document, il n'est pas fait usage des caches des implémentations JPA utilisées. Celui d'EclipseLink semble être actif par défaut et provoquait des erreurs dans les tests.

@Entity
@Table(name = ConfigJdbc.TAB_CATEGORIES)
@JsonFilter("jsonFilterCategorie")
@Cache(alwaysRefresh = true)
public class Categorie implements AbstractCoreEntity {
  • toutes les annotations [@OneToMany] sont accompagnées de l'annotation [@CascadeOnDelete] :

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

Cette annotation joue un rôle lors de la génération des tables à partir des entités JPA. Elle ajoute aux clés étrangères (ici PRODUITS[CATEGORIE_ID] ---> CATEGORIES[ID]) l'attribut SQL [ON DELETE CASCADE] qui fait qu'à chaque fois qu'on supprime une catégorie dans la table [CATEGORIES], les produits correspondants dans la table [PRODUITS] sont eux aussi supprimés ;

Note : il est important de noter que cette annotation est utilisée aussi bien lors de la création de la table comme on vient de le voir qu'en exploitation de celle-ci. EclipseLink suppose que l'attribut SQL [ON DELETE CASCADE] est bien présent et il l'utilise à chaque fois qu'on lui demande de supprimer une catégorie. Son absence provoquerait des erreurs.

7.6. La couche de tests

  

Les tests ci-dessus sont identiques à ceux des implémentations Spring JDBC et Spring JPA Hibernate. On se reportera aux pages suivantes si nécessaire :

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

Les résultas obtenus sont les suivants :

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

Le test [JUnitTestProxies] donne les résultats console suivants :

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]

On voit ici, que lorsqu'on accède au champ [Categorie.produits] d'une catégorie de type PROXY et au champ [Produit.categorie] d'un produit de type PROXY, on réussit à obtenir l'information dans les deux cas (lignes 7 et 17). Parmi les trois implémentations JPA, c'est la seule qui permet cela sur les entités PROXY.