JPA avec Spring

Dans cet article, nous allons décrire le développement d’une couche DAO[1] avec Spring et JPA[2].

Implémentation des DAO

JpaDaoSupport et JpaTemplate

Une première solution consistait à créer une classe DAO qui hérite de la classe JpaDaoSupport de Spring.

Cette solution a disparu depuis Spring Framework 4.

Ecrire des DAO "Plain JPA"

La seconde solution consiste à écrire des DAO qui n’utilisent pas les classes Spring. Cette approche est intéressante car d’une part, elle semblera plus naturelle à un développeur JPA, et d’autre part, elle permet la configuration par annotation.

Avec cette solution, Spring injecte directement une instance "entity manager" dans notre DAO en exploitant l’annotation JPA @PersistenceContext.

@Repository
public class ProductDao {

  @PersistenceContext
  private EntityManager em;

  public List<Product> findByTitleLike(String title) {
    return em.createQuery(
            "select p from Product as p where p.title like :title",
            Product.class)
        .setParameter("title", title + '%')
        .getResultList();
  }
  ...
}
  • Bien que Spring injecte un "entity manager", nous n’aurons à déclarer du point de vue de Spring uniquement un bean de type EntityManagerFactory, pas EntityManager.

  • Spring permet d’injecter un EntityManagerFactory, notamment grâce à l’annotation @PersistenceUnit, mais les DAO doivent alors se charger de récupérer l’entity manager, ce qui alourdit le code

Déclarer l’entity manager factory

Pour déclarer l’entity manager factory, il faut d’abord considérer l’environnement d’exécution de notre code, à savoir :

  • Application standalone (cas d’une application Spring Boot, batch, test JUnit,…​)

  • Serveur Tomcat

  • Serveur Java EE / Jakarta EE (Glassfish, JBoss / WildFly,…​)

Cas d’une application standalone

Caractéristiques de l’environnement 

  • La connexion n’est pas gérée nativement par un pool : d’où l’utilisation du bean dataSource de type DataSource

  • Il n’existe pas nativement un conteneur JPA, permettant entre autres d’instancier l’entity manager factory : d’où le bean de type LocalContainerEntityManagerFactoryBean

Fichier XML de configuration Spring
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
                https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/jee
                https://www.springframework.org/schema/jee/spring-jee.xsd
            http://www.springframework.org/schema/context
                https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="info.jtips.spring"/>

    <bean id="dataSource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="jdbc:postgresql://localhost:5432/jtips"/>
        <property name="username" value="jtips" />
        <property name="password" value="jtipspwd" />
    </bean>

    <bean id="emf"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

Application Tomcat

Caractéristiques de l’environnement :

  • La connexion est gérée par un pool de connexion déclaré sous Tomcat : d’où la récupération du bean dataSource par lookup JNDI.

  • Il n’existe pas nativement un conteneur JPA, permettant entre autres d’instancier l’entity manager factory : d’où le bean de type LocalContainerEntityManagerFactoryBean.

Fichier XML de configuration Spring 
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
                https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/jee
                https://www.springframework.org/schema/jee/spring-jee.xsd
            http://www.springframework.org/schema/context
                https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="info.jtips.spring"/>

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/LibrairieDS"/>

    <bean id="emf"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

Application Java EE / Jakarta EE

Caractéristiques de l’environnement :

  • La connexion est gérée par un pool de connexion déclaré sous le serveur d’application : d’où la récupération du bean dataSource par lookup JNDI.

  • Le serveur intègre nativement un conteneur JPA permettant l’instanciation de l’entity manager factory : on ne déclare donc pas l’entity manager factory avec Spring.

Fichier XML de configuration Spring
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
                https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/jee
                https://www.springframework.org/schema/jee/spring-jee.xsd
            http://www.springframework.org/schema/context
                https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="info.jtips.spring"/>

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/LibrairieDS"/>

</beans>

Configuration JPA

Il s’agit de renseigner le fichier persistence.xml placé dans le répertoire META-INF de l’archive de déploiement. Encore une fois, la configuration dépend de l’environnement d’exécution.

Cas d’une application standalone

Cet environnement d’exécution ne fournit pas de gestionnaire transationnel JTA[3]. On positionne donc l’attribut transation-type à RESOURCE_LOCAL.

La data source est pour sa part spécifiée dans la configuration Spring. Par ailleurs, comme on utilise un conteneur Spring / JPA (le bean LocalContainerEntityManagerFactoryBean), il faut préciser l’implémentation JPA à utiliser via l’élément provider.

Fichier persistence.xml
<persistence>
  <persistence-unit name="jtips" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
  </persistence-unit>
</persistence>

Application Tomcat

La configuration est identique au cas d’une application standalone.

Fichier persistence.xml
<persistence>
  <persistence-unit name="jtips" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
  </persistence-unit>
</persistence>

Application Java EE / Jakarta EE

Les serveurs d’application Java EE fournissent un gestionnaire transationnel de type JTA. On positionne donc l’attribut transation-type à JTA et on utilise l’élément jta-data-source pour déclarer la data source. Par ailleurs, les serveurs Java EE sont livrés avec une implémentation JPA. Il n’est donc pas nécessaire de spécifier le provider JPA.

Fichier persistence.xml
<persistence>
  <persistence-unit name="jtips" transaction-type="JTA">

    <jta-data-source>java:/jdbc/LibrairieDS</jta-data-source>

  </persistence-unit>
</persistence>

Références


1. Data Access Object
2. Java Persistence API
3. Java Transaction API