Spring/JPA

Introduction

Dans cet article, nous allons décrire le développement d'une couche DAO avec Spring et JPA.

Implémentation des DAO

JpaDaoSupport et JpaTemplate

Une première solution consiste à créer une classe DAO qui hérite de classe JpaDaoSupport de Spring. Par héritage, notre DAO dispose notamment d'un accès à une instance de JpaTemplate qui sera utilisée par délégation pour exécuter les requêtes JPA.

A noter que cette approche existe essentiellement pour des raisons historiques et elle ne permet pas aisément la configuration par annotation. Elle est de ce fait déconseillée au profit de l'approche "plain JPA".

La classe DAO :

public class ProduitDaoImpl extends JpaDaoSupport implements ProduitDao {

  public List<Produit> findProduit(String titre) {
    Map<String, String> params = new HashMap<String, String>();
    params.put("ptitre", titre);
    String query = "select p from Produit as p where p.titre like :ptitre";
    return getJpaTemplate().findByNamedParams(query, params);
  }
  ...
}

La déclaration du bean DAO est effectuée avec la syntaxe XML. Il suffit de lui injecter le bean "entity manager factory" sur la propriété "entityManagerFactory" disponible via la classe JpaDaoSupport.

<bean id="produitDao" class="fr.icodem.librairie.dao.ProduitDaoImpl">
  <property name="entityManagerFactory" ref="myEmf"/>
</bean>

Nous verrons comment déclarer le bean "myEmf" un peu plus loin.

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("produitDao")
public class ProduitDaoImpl implements ProduitDao {
 
  @PersistenceContext
  private EntityManager em;

  public List<Produit> findProduit(String titre) {
    String jpql = "select p from Produit as p where p.titre like :ptitre"; 
    Query query = em.createQuery(jpql);
    query.setParameter("titre", titre);
    return query.getResultList(); 
  }
  ...
}

Remarques :

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 :

Cas d'une application standalone

Caractéristiques de l'environnement :

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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                          http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
                          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:annotation-config/>
  <context:component-scan base-package="fr.icodem.librairie"/>

  <bean id="librairieDS"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/librairie"/>
    <property name="username" value="root" />
    <property name="password" value="" />
  </bean>
 
  <bean id="emf"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
  </bean>

</beans>

Application Tomcat

Caractéristiques de l'environnement :

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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                          http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
                          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:annotation-config/>
  <context:component-scan base-package="fr.icodem.librairie"/>

  <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 JEE 5

Caractéristiques de l'environnement :

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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                          http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
                          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:annotation-config/>
  <context:component-scan base-package="fr.icodem.librairie"/>

  <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 (Java Transaction API). 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="librairie" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>

    <class>org.librairie.model.Categorie</class>
    <class>org.librairie.model.Produit</class>
    <exclude-unlisted-classes/>
  </persistence-unit>
</persistence>

Application Tomcat

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

Fichier persistence.xml :

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

    <class>org.librairie.model.Categorie</class>
    <class>org.librairie.model.Produit</class>
    <exclude-unlisted-classes/>
  </persistence-unit>
</persistence> 

Application JEE 5

Les serveurs d'application JEE 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 JEE 5 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="librairie" transaction-type="JTA">

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

    <class>org.librairie.model.Categorie</class>
    <class>org.librairie.model.Produit</class>
    <exclude-unlisted-classes/>
  </persistence-unit>
</persistence>