Le Touilleur Express

  • Accueil
  • A propos de l’auteur
  • A propos du Touilleur Express

JPA et Maven : gérer 2 persistence.xml distincts

17 juin, 2010

Licence Commons Creatives voir http://www.flickr.com/photos/bofh/31729024/sizes/s/Je vous propose un petit article technique sur Maven2, JPA et plus particulièrement la gestion de plusieurs persistence.xml. Cet article intéressera les personnes qui ont un projet avec JPA, et qui souhaitent gérer une version pour le packaging final et une version pour les tests unitaires ou d’intégrations. J’ai eu ce cas lors de la mise en place de tests Fits, où nous souhaitions pouvoir utiliser un profil JPA pour les tests avec H2, capable de fonctionner en mode « base de données en mémoire/base de données serveur TCP », et un profil classique avec Oracle.

Gérer 2 datasources

Dans mon exemple, je souhaite gérer 2 datasources distinctes : une pour l’exécution de l’application, et une autre pour l’exécution des tests unitaires. Lors de l’exécution de mon application, mes classes sont annotées et j’ai donc aussi besoin que le moteur JPA trouve celles-ci, afin de les instrumenter. Une datasource utilise une base Oracle, une autre datasource utilise le mode « in-memory » d’h2.

Approche simple

La première approche est simple :
– écrire un fichier générique persistence.xml dans src/main/resources/META-INF
– déclarer des propriétés dans votre pom.xml
– éventuellement avoir un profil maven pour les tests et un profil pour l’exécution de l’application

<?xml version="1.0" encoding="UTF-8"?>
<persistence 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"
             version="1.0">

    <persistence-unit name="sample-db" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <properties>
            <property name="hibernate.dialect" value="${hibernate.dialect}"/>
            <property name="hibernate.hbm2ddl.auto" value="${jdbc.hbm2ddl.auto}"/>
            <property name="hibernate.show_sql" value="${jdbc.show_sql}"/>
            <property name="hibernate.format_sql" value="${jdbc.format_sql}"/>
            <property name="hibernate.connection.url" value="${jdbc.url}"/>
            <property name="hibernate.connection.driver_class" value="${jdbc.driver}"/>
            <property name="hibernate.connection.username" value="${jdbc.username}"/>
            <property name="hibernate.connection.password" value="${jdbc.password}"/>
        </properties>
    </persistence-unit>

</persistence>

Le fichier pom.xml contiendra ensuite 2 profils. Un profil permet d’activer le mode « base de données en mémoire » avec H2, pratique pour les tests unitaires. Un deuxième mode permet de se connecter via TCP sur une instance d’H2 démarrée en local, pratique pour vérifier l’état de la base à posteriori, sans utiliser DbUnit pour l’instant.

Fichier pom.xml :

<project>
   ...
   ...
  <profiles>
        <profile>
            <id>h2-tcp-db</id>
            <properties>
                <jdbc.url>jdbc:h2:tcp://localhost/~/test_db;MODE=Oracle</jdbc.url>
                <jdbc.driver>org.h2.Driver</jdbc.driver>
                <jdbc.username>sa</jdbc.username>
                <jdbc.password></jdbc.password>
                <jdbc.format_sql>false</jdbc.format_sql>
                <jdbc.show_sql>false</jdbc.show_sql>
                <jdbc.hbm2ddl.auto>create</jdbc.hbm2ddl.auto>
            </properties>
        </profile>

        <profile>
            <id>h2-inmemory-db</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <jdbc.url>jdbc:h2:mem:test_db;MODE=Oracle;DB_CLOSE_ON_EXIT=FALSE</jdbc.url>
                <jdbc.driver>org.h2.Driver</jdbc.driver>
                <jdbc.username>sa</jdbc.username>
                <jdbc.password></jdbc.password>
                <jdbc.format_sql>false</jdbc.format_sql>
                <jdbc.show_sql>false</jdbc.show_sql>
                <jdbc.hbm2ddl.auto>create-drop</jdbc.hbm2ddl.auto>
            </properties>
        </profile>
	</profiles>
</project>

Cela répond aux cas simples. Si par contre vous devez gérer plusieurs formats de fichier persistence.xml, cela se complique un peu. Il est possible de déclarer une datasource pour les tests, une autre pour l’exécution. Jusqu’ici pas de soucis. Mais avouez que packager une datasource de tests dans l’application finale, ce n’est pas top non ?

Approche avec 2 fichiers persistence.xml

J’ai cherché à configurer Maven et mon environnement afin d’avoir un fichier persistence.xml dans mon répertoire src/main/resources/META-INF pour la prod, et un deuxième fichier de persistence dans src/test/resources/META-INF pour mes tests unitaires et mes tests d’intégrations.

Armé de ma meilleur volonté, j’ai commencé par déposer un deuxième fichier persistence.xml dans le répertoire src/test/resources/META-INF en me disant que ce serait bon… Et bien non.

Le fichier est copié lors de la phase « test » comme prévu, les variables sont remplacées si vous n’avez pas touché au plugin maven-resource.

<!-- fichier pom.xml -->
<project>
...
...
<build>
	<resources>
		<resource>
			<directory>src/main/resources</directory>
			<filtering>true</filtering>
		</resource>
	</resources>

	<testResources>
		<testResource>
		<directory>src/test/resources</directory>
		<filtering>true</filtering>
			<excludes>
			<exclude>**/fit/**</exclude>
			<exclude>**/reports/**</exclude>
			</excludes>
		</testResource>
	</testResources>
</build>
...
</project>

Tout fonctionne du côté de Maven, mais par cotnre le mapping JPA ne fonctionne plus. En effet, le fichier étant déposé dans target/test-classes, et ce répertoire ne contenant pas mes entités annotées, JPAne peut rien faire. La solution semble donc de trouver le moyen de dire à JPA où trouver les classes (dans target/classes).

Le tag jar-file permet de spécifier l’emplacement d’un jar ou d’un répertoire différent pour JPA, qui sera utilisé afin d’y trouver les classes annotées.
Voici le fichier persistence.xml tel que je souhaite qu’il soit au final dans mon répertoire target/test-classes/META-INF :

<?xml version="1.0" encoding="UTF-8"?>

<!-- Fichier final dans target/test-classes/META-INF -->

<persistence 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"
             version="1.0">

    <persistence-unit name="test_db" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <jar-file>file:/C:/Dev/monprojet/target/classes</jar-file>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="${jdbc.hbm2ddl.auto}"/>
            <property name="hibernate.show_sql" value="${jdbc.show_sql}"/>
            <property name="hibernate.format_sql" value="${jdbc.format_sql}"/>
            <property name="hibernate.connection.url" value="${jdbc.url}"/>
            <property name="hibernate.connection.driver_class" value="${jdbc.driver}"/>
            <property name="hibernate.connection.username" value="${jdbc.username}"/>
            <property name="hibernate.connection.password" value="${jdbc.password}"/>
        </properties>
    </persistence-unit>
</persistence>


Notez que pour le tag jar-file, il faut utiliser une URI, pas un chemin technique. Ce qui va compliquer un peu notre travail dans quelques instants. La question est maintenant la suivante : comment remplacer file:/C:/Dev/monprojet/ par le répertoire de base de Maven ? le tout sous la forme d’une URL ?

Euréka, Maven depuis la version 2.1 propose une propriété project.baseUri qui correspond exactement à ce que je veux au final : file:/C:/Dev/monprojet/.
Je remplace alors le tout dans mon fichier test/resources/META-INF/persistence.xml comme suit (attention au dernier slash qui est ajouté systématiquement):

<?xml version="1.0" encoding="UTF-8"?>
<persistence 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"
             version="1.0">

    <persistence-unit name="test_db" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <jar-file>${projectBaseUri}target/classes</jar-file>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="${jdbc.hbm2ddl.auto}"/>
            <property name="hibernate.show_sql" value="${jdbc.show_sql}"/>
            <property name="hibernate.format_sql" value="${jdbc.format_sql}"/>
            <property name="hibernate.connection.url" value="${jdbc.url}"/>
            <property name="hibernate.connection.driver_class" value="${jdbc.driver}"/>
            <property name="hibernate.connection.username" value="${jdbc.username}"/>
            <property name="hibernate.connection.password" value="${jdbc.password}"/>
        </properties>
    </persistence-unit>
</persistence>

Je lance mvn test, et je vais voir le résultat : ma variable n’est pas remplacée, elle reste positionnée à project.baseUri… Bon, vieux réflexe de gars qui connait bien Maven 2, allons voir sur les bugs s’il n’y aurait pas un beau bug dans la dernière version de Maven…

Trop facile, je trouve celui-ci : MRESOURCES-99 ${project.baseUri} and ${maven.build.timestamp} are not expanded by resource filtering

Il y a un workaround simple : déclarer la propriété dans votre pom.xml comme ci-dessous :

  <properties>
    <timestamp>${maven.build.timestamp}</timestamp>
    <projectBaseUri>${project.baseUri}</projectBaseUri>
</properties>

Et là enfin, tout fonctionne. Un petit « mvn test -Ph2-inmemory-db » et je lance mes tests en mémoire avec un fichier persistence.xml dédié.

Pour terminer, voici mon fichier persistence.xml final, tel qu’il est actuellement :

<?xml version="1.0" encoding="UTF-8"?>
<persistence 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"
             version="1.0">

    <persistence-unit name="test_db" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <!-- When using test mode, you must introspect the Entities in a different folder, so that create schema works -->
        <!-- There is a bug in maven 2.2.x http://jira.codehaus.org/browse/MRESOURCES-99 so I added a property in the main pomx.xml-->
        <jar-file>${projectBaseUri}target/classes</jar-file>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="${jdbc.hbm2ddl.auto}"/>
            <property name="hibernate.show_sql" value="${jdbc.show_sql}"/>
            <property name="hibernate.format_sql" value="${jdbc.format_sql}"/>
            <property name="hibernate.connection.url" value="${jdbc.url}"/>
            <property name="hibernate.connection.driver_class" value="${jdbc.driver}"/>
            <property name="hibernate.connection.username" value="${jdbc.username}"/>
            <property name="hibernate.connection.password" value="${jdbc.password}"/>

            <!-- other hibernate properties for historization -->
            <property name="hibernate.ejb.event.post-insert"
                      value="org.hibernate.ejb.event.EJB3PostInsertEventListener,org.hibernate.envers.event.AuditEventListener"/>
            <property name="hibernate.ejb.event.post-update"
                      value="org.hibernate.ejb.event.EJB3PostUpdateEventListener,org.hibernate.envers.event.AuditEventListener"/>
            <property name="hibernate.ejb.event.post-delete"
                      value="org.hibernate.ejb.event.EJB3PostDeleteEventListener,org.hibernate.envers.event.AuditEventListener"/>
            <property name="hibernate.ejb.event.pre-collection-update"
                      value="org.hibernate.envers.event.AuditEventListener"/>
            <property name="hibernate.ejb.event.pre-collection-remove"
                      value="org.hibernate.envers.event.AuditEventListener"/>
            <property name="hibernate.ejb.event.post-collection-recreate"
                      value="org.hibernate.envers.event.AuditEventListener"/>
        </properties>
    </persistence-unit>
</persistence>

Conclusion
Voilà, je ne sais pas si cela pourra vous servir. Je me sers aussi du Touilleur Express comme d’un cahier où je note ce que je trouve, c’était l’idée au départ du blog. Si vous avez d’autres idées, n’hésitez pas à contribuer via les commentaires.

Articles similaires:

Default ThumbnailAnt et SQL Default ThumbnailComment gérer les ressources JAR de vos projets J2EE Default ThumbnailJPA et tests d’intégrations Default ThumbnailHibernate : gérer le chargement des associations efficacement
  • Khanh 17 juin 2010 at 9 h 18 min

    Cool. Merci pour l’astuce.
    Sinon, pour ma part (et je ne sais pas si cela peut répondre à ta problématique), j’avais utilisé les classifier pour générer des jars différents et avoir une gestion plus fine au niveau de mon scope.
    J’ai essayé d’en parler sur mon blog : http://jetoile.blogspot.com/2010/01/retour-sur-la-mise-en-uvre-d_04.html

    Hope this helps… 😉

  • Olivier 17 juin 2010 at 11 h 13 min

    A noter aussi que si on utilise spring, on est pas obliger de mettre la la conf de connexion à la base dans le persistence.xml. On peut la laisser dans les fichiers de conf spring, et donc avoir une conf de test différente.

  • Fred Bricon 17 juin 2010 at 11 h 23 min

    Cool, je connaissais pas le coup du .

    Apparemment Hibernate est assez souple quant à son utilisation, chez moi ça marche directement avec un chemin absolu :
    ${project.build.outputDirectory}

  • Fred Bricon 17 juin 2010 at 11 h 26 min

    Arf l\’aime pas les tags dans le commentaire.
    Je parlais bien evidemment du tag jar-file

  • Robin Komiwes 17 juin 2010 at 11 h 45 min

    Dans le même esprit que ce genre de situation, on développe en ce moment un outil qui permettrait de gérer les évolutions du model, et ceci indépendamment de la BDD choisie.

    Le projet est basé sur les dialect hibernate et tapestry-ioc (non pas tapestry-core qui est le framework web) et on devrait arriver à fournir un plugin maven.

    C’est largement inspiré des migrations de Ruby on Rails.

    http://github.com/spreadthesource/tapestry5-db-migrations

  • Piwaï 18 juin 2010 at 11 h 06 min

    @Robin ça c’est un vrai besoin, je ne comprends pas que ça ne soit pas plus adressé pour le moment dans le monde Java.

    Le jour où j’ai découvert les migrations de BD avec RoR, j’ai trouvé ça génial. Et étrange de ne le retrouver nul part ailleurs… Une de ses grandes forces, c’est d’utiliser un DSL en ruby pour décrire le schéma de donnée.

Derniers articles

  • Vis ma vie de Staff/Principal Engineer

    Suite de l’article précédent sur le Staff Engineer. Aujourd’hui, voyons un peu

    20 juillet, 2022
  • Inari

    Devenir Staff Engineer : comment et pourquoi ?

    Après une dizaine d’années en tant que développeur, vous serez un jour

    17 juillet, 2022
  • WeAreDevelopers 2022, conférence à Berlin – jour 1

    Il est 8h40, 19 degrés, vous êtes à Berlin. La queue dehors

    24 juin, 2022

Tweets @nmartignole

  • RT  @katecrawford : Umm, anyone a little concerned that Bard is saying its training dataset includes... Gmail? I'm assuming that's flat out…

    2 days ago
  • Je découvre qu’ils apprennent le SQL en Terminal, très intéressant https://t.co/MrfcHve9wo

    3 days ago
  • RT  @AmelieBenoit33 : Je m’essaye à de nouveaux formats ! Un premier sketch qui me trottait en tête depuis le sketchnote précédent; la techn…

    3 days ago

Mots clés

Apple (32) Architecture (13) Big Data (5) Conference (8) Devoxx (55) Dev Web (37) Doctolib (2) geekevent (1) groovy (2) Innoteria (11) Java (517) Linux (10) Non classé (14) Perso (266) Recrutement (3) Scala (30) scrum (43) Société (3) Startup (20) Web 2.0 (67)

Le Touilleur Express

Blog par Nicolas Martignole

Contactez-moi : nicolas@touilleur-express.fr

Suivez-moi sur Twitter : @nmartignole

Copyright© 2008 - 2020 Nicolas Martignole | Tous droits réservés
  • A propos de l’auteur
  • A propos du Touilleur Express
  • Log In
  • My Account
  • My Profile
  • Reset Password

Le Touilleur Express