Lorsque vous interagissez avec une base de données, vous utilisez souvent le design pattern DAO (Data Access Objet) et l'API JDBC (Java Database Connectivity).
Dans ce chapitre, je vais vous montrer comment créer vos DAO en respectant les principes SOLID et comment les configurer avec Spring IoC.
Création et configuration des DAO
Les DAO sont à créer dans le module ticket-consumer qui, je vous le rappelle, est responsable des relations avec les services extérieurs à l'application, notamment la base de données.
Pour les classes DAO, je vais suivre le même principe que pour les Managers : je crée une classe DAO par sous-packages de beans métier.
Bien entendu, il ne faut pas oublier l'inversion de dépendances et les abstractions donc, chaque classe DAO (suffixée par DaoImpl
) implémente une interface DAO (suffixée par Dao
).
package org.example.demo.ticket.consumer.contract.dao;
/**
* Interface DAO du package
* {@link org.example.demo.ticket.model.bean.ticket}
*/
public interface TicketDao {
// ...
}
package org.example.demo.ticket.consumer.impl.dao;
import org.example.demo.ticket.consumer.contract.dao.TicketDao;
/**
* Classe d'implémentation de {@link TicketDao}.
*/
@Named
public class TicketDaoImpl extends AbstractDaoImpl implements TicketDao {
// ...
}
Afin de généraliser certains éléments, toutes les classes DAO vont hériter de la classe AbstractDaoImpl
qui aura, entre autres, un attribut dataSource
permettant la connexion à la base de données de l'application.
package org.example.demo.ticket.consumer.impl.dao;
import javax.sql.DataSource;
public abstract class AbstractDaoImpl {
private DataSource dataSource;
protected DataSource getDataSource() {
return dataSource;
}
// ...
}
Configuration des DAO
Je veux que la DataSource soit injectée dans l'attribut dataSource
des classes d'implémentation des DAO.
Pour faire cela facilement, je dis à Spring d'injecter le bean dataSourceTicket
via les annotations @Inject
et @Named("dataSourceTicket")
directement sur l'attribut dataSource
dans la classe AbstractDaoImpl
:
package org.example.demo.ticket.consumer.impl.dao;
import javax.inject.Inject;
import javax.inject.Named;
import javax.sql.DataSource;
public abstract class AbstractDaoImpl {
@Inject
@Named("dataSourceTicket")
private DataSource dataSource;
// ...
}
Configuration de la DataSource
Il ne reste plus qu'à créer la DataSource d'accès à la base de données DB_TICKET que Spring doit injecter (bean dataSourceTicket).
Drivers JDBC
Afin de se connecter à une base de données avec JDBC, les drivers JDBC correspondant au SGBD (Système de Gestion de Base de Données) doivent être accessibles par le classloader lors de la création de la DataSource.
Dans le fichier pom.xml
racine, j'ajoute :
<project>
...
<dependencyManagement>
<dependencies>
<!-- Drivers JDBC PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1212</version>
<scope>runtime</scope>
</dependency>
...
</dependencies>
</dependencyManagement>
</project>
Module ticket-batch
Pour les batches, je vais gérer moi-même la création de la DataSource. Je dois donc ajouter les drivers JDBC de PostgreSQL dans les dépendances Maven du module ticket-batch.
Dans le fichier ticket-batch/pom.xml
, j'ajoute :
<project>
...
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
...
</dependencies>
</project>
Module ticket-webapp
Pour l'application web, en revanche, il est possible :
soit d'ajouter le JAR des drivers JDBC au classpath de Tomcat (typiquement dans
$CATALINA_HOME/lib
) ;soit de l'embarquer directement dans le WAR (dans
WEB-INF/lib
).
C'est ce que je vais faire ici : embarquer directement les drivers dans le WAR. J'ajoute donc la dépendance Maven dans le fichier ticket-webapp/pom.xml
:
<project>
...
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
...
</dependencies>
</project>
Configuration de l'application web
Le serveur Apache Tomcat, comme la majorité des serveurs d'application, permet de créer des ressources JNDI de DataSource s'appuyant sur un pool de connexion.
Ces ressources peuvent être configurées :
soit au niveau du serveur Tomcat,
soit directement dans le WAR de déploiement de la webapp (cf documentation).
Je vais utiliser la dernière solution ici, qui est plus facile à mettre en oeuvre dans cette démonstraction (je n'ai pas besoin de rentrer le détail de la configuration de Tomcat).
Pour configurer les ressources directement dans le WAR, il faut ajouter un fichier /META-INF/context.xml
dans celui-ci.
Afin de l'embarquer facilement lors du build Maven, je crée le fichier src/main/webapp/META-INF/context.xml
dans le module ticket-webapp :
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource name="jdbc/DB_TICKET"
auth="Container"
type="javax.sql.DataSource"
url="jdbc:postgresql://localhost:5432/db_ticket"
driverClassName="org.postgresql.Driver"
username="ticket"
password="ticket"
defaultAutoCommit="false"
defaultTransactionIsolation="READ_COMMITTED"
initialSize="1"
maxTotal="30"
maxIdle="10"
maxWaitMillis="60000"
minIdle="1"
removeAbandonedTimeout="60"
removeAbandonedOnBorrow="true"
logAbandoned="true"
minEvictableIdleTimeMillis="10000"
timeBetweenEvictionRunsMillis="30000"
validationQuery="SELECT 1"
testWhileIdle="true"
testOnBorrow="true"
/>
</Context>
Chaque ressource JNDI à créer fait l'objet d'un élément <Resource>
dans l'élément racine <Context>
. J'ai formaté ici l'élément <Resource>
en 3 blocs d'attributs :
le premier définit la nature et le nom de la ressource JNDI ;
le deuxième définit les paramètres de connexion à la base de données ;
le troisième définit les paramètres de configuration du pool de connexion (Apache Commons DBCP).
La ressource JNDI est maintenant configurée dans Tomcat, il ne reste plus qu'à utiliser cette ressource JNDI dans le module ticket-webapp :
Je commence par indiquer dans le fichier
src/main/webapp/WEF-INF/web.xml
que j'ai besoin de la ressource JNDIjdbc/DB_TICKET
:<web-app> <resource-ref> <res-ref-name>jdbc/DB_TICKET</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> ... </web-app>
Puis, j'ajoute cette ressource en tant que bean
dataSourceTicket
dans l'IoC container de Spring. J'ajoute dans le fichiersrc/main/resources/.../webappContext.xml
:<bean id="dataSourceTicket" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/DB_TICKET"/> </bean>
Configuration des batches
En ce qui concerne les batches, il n'y a pas de serveur d'application pour gérer les ressources JNDI. Je vais embarquer et gérer un pool de connexion directement dans l'application.
Je vais utiliser le pool de connexion Apache Commons DBCP. J'ajoute donc la dépendance Maven :
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>
Afin de configurer le pool de connexion, je vais utiliser un fichier properties.
J'aborde cette problématique dans le chapitre Packagez vos livrables du cours Organisez et packagez une application Java avec Apache Maven. Pour rappel, le principe est le suivant :
Je crée un répertoire
src/data/conf
dans le module ticket-batch contenant un exemple des fichiers de configuration.Lors du build Maven, le plugin
maven-assembly-plugin
crée une archive avec tous les JAR nécessaires et ces fichiers de configuration.L'administrateur décompresse l'archive sur le serveur de production et modifie les valeurs dans les fichiers de configuration.
Je crée donc le fichier src/data/conf/db-ticket.properties
contenant les propriétés de configuration du pool de connexion à la base DB_TICKET :
url=jdbc:postgresql://localhost:5432/db_ticket
driverClassName=org.postgresql.Driver
username=ticket
password=ticket
defaultAutoCommit=false
defaultTransactionIsolation=READ_COMMITTED
initialSize=1
maxTotal=30
maxIdle=10
maxWaitMillis=60000
minIdle=1
removeAbandonedTimeout=60
removeAbandonedOnBorrow=true
logAbandoned=true
minEvictableIdleTimeMillis=10000
timeBetweenEvictionRunsMillis=30000
validationQuery=SELECT 1
testWhileIdle=true
testOnBorrow=true
Afin de faciliter l'adressage des fichiers de l'application, mes scripts de lancement des batches assignent la propriété système application.home
indiquant la racine de l'application (le répertoire où a été décompressée l'archive de déploiement).
Dans le fichier src/main/resources/.../batchContext.xml
du module ticket-batch, j'importe le fichier properties de configuration de la base de données et je crée la datasource dataSourceTicket
:
<beans>
<!-- Chargement du fichier properties contenant
la configuration de la datasource vers DB_TICKET -->
<bean id="dataSourceTicketConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="file:${application.home}/conf/db-ticket.properties"/>
</bean>
<!-- Création de la datasource "dataSourceTicket" -->
<bean id="dataSourceTicket"
class="org.apache.commons.dbcp2.BasicDataSourceFactory"
factory-method="createDataSource"
destroy-method="close">
<constructor-arg ref="dataSourceTicketConfiguration"/>
</bean>
</beans>
Dans cette configuration Spring, je crée :
une instance de
java.util.Properties
(bean dataSourceTicketConfiguration) à partir du fichier propertiesfile:${application.home}/conf/db-ticket.properties
;une instance de
org.apache.commons.dbcp2.BasicDataSource
(bean dataSourceTicket).
Le bean dataSourceTicket
est créé :
grâce à la méthode static
createDataSource
(attribut factory-method),de la classe
org.apache.commons.dbcp2.BasicDataSourceFactory
(attribut class),en lui passant en paramètre le bean
dataSourceTicketConfiguration
(élément constructor-arg).
L'attribut destroy-method indique que la méthode close
devra être appelée sur ce bean lorsqu'il sera détruit (typiquement à l'arrêt de l'application).
Ça y est, tout est prêt ! Cela vous a peut-être semblé un peu long, mais je tenais à bien vous expliquer chaque étape. Ne vous inquiétez pas, en réalité (et d'autant plus avec l'habitude), ce travail préparatoire ne prend que quelques minutes. ;)
Dans le chapitre suivant, je vais passer aux choses sérieuses : exécuter des requêtes SQL... mais en utilisant Spring JDBC !