| Remarque | ||
|---|---|---|
| ||
A revoir |
Par défaut, les applications esup-blank et esup-example sont configurées avec les gestionnaires de base de données s'appuyant sur Hibernate.
Les gestionnaires de bases de données
Le choix du gestionnaire de base de données Hibernate doit se faire en fonction de la maîtrise qu'a le développeur de la base de données. Deux cas se présentent :
- Le développeur a la totale maîtrise de la base de données, c'est lui qui la fait évoluer en fonction des besoins de son application. Il utilisera dans ce cas l'implémentation UgradableHibernateDatabaseManagerImpl. Cette implémentation lui permettra de faire évoluer la structure de sa base de données en modifiant son mapping, de manière automatique sans même toucher au code SQL.
- Le développeur n'a pas la maîtrise de la base de données : il s'agit d'une base de données institutionnelle, ou bien encore d'une base maîtrisée par une autre application. Il utilisera dans ce cas l'implémentation BasicHibernateDatabaseManagerImpl, et devra alors faire coller son mapping aux structures de la base de données.
- Note : ne pas avoir la maîtrise de la base signifie que l'on ne fait que du select dessus, aucune mise à jour n'est possible.
Installation de JBossTools Hibernate (si partie toujours valide apres réfection) |
| Astuce | ||
|---|---|---|
| ||
Relecture RB |
Sommaire :
| Sommaire | ||
|---|---|---|
|
...
Les exemples d'accès aux données de esup-example utilisent JPA. Il existe plusieurs implémentations de JPA. Celle qui est retenue est celle de Hibernate.
Note : L'implémentation Hibernate permet d'utiliser des fichiers de mapping (objet/base de données) spécifiques à Hibernate ce qui offre une compatibilité ascendante avec ESUP-Commons V1. De même, Hibernate permet potentiellement de faire des choses en plus de ce que la norme JPA prévoit. Mais ces deux fonctionnalités, si elles sont utilisées, ne permettent pas de passer à une autre implémentation de JPA. C'est la raison pour laquelle dans, esup-example, nous nous limitons à une utilisation stricte de JPA.
Le gestionnaire d'entités
En JPA, l’élément qui permet de manipuler les objets en base de données est le gestionnaire d'entités. Les objets sont annotés afin de préciser comment ils doivent être enregistés en base de données.
La déclaration se fait dans le fichier resourcesOn trouvera par exemple dans le fichier /properties/dao/dao.xml, pour une base maîtrisée par l'application :
| Bloc de code |
|---|
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <bean id="databaseManager1txManager" class="[org.springframework.orm.]jpa.hibernate.UpgradableHibernateDatabaseManagerImplJpaTransactionManager" > <property <property name="sessionFactoryBeanNameentityManagerFactory" valueref="sessionFactoryentityManagerFactory" /> </bean> <bean id="entityManagerFactory" <property class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="${datasource.bean}" /> <property name="createSessionFactoryBeanNamejpaVendorAdapter" ref="jpaVendorAdapter" /> <property name="persistenceXmlLocation" value="createSessionFactoryclasspath:/properties/dao/persistence.xml" /> <property <property name="upgradableSessionFactoryBeanNamejpaProperties" valueref="upgradableSessionFactoryjpaProperties" /> </bean> |
Comme on le voit, on indique au gestionnaire de base de données quelles sont les session factories (« usines à sessions ») à utiliser pour accéder à la base de données, en créer les structures et les mettre à jour.
...
<bean id="JDBCDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" lazy-init="true" |
...
>
<property name="driverClassName" value="${jdbc.connection.driver_class}" />
<property name="maxActive" value="100" />
<property name="maxIdle" value="30" />
<property name="maxWait" value="100" />
<property name="url" value="${jdbc.connection.url}" />
<property name="username" value="${jdbc.connection.username}" />
<property name="password" value="${jdbc.connection.password}" />
</bean>
<jee:jndi-lookup id="JNDIDataSource" jndi-name="${jndi.datasource}" lookup-on-startup="false" expected-type="javax.sql.DataSource"/>
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
<property name="database" value="${jpa.database.type}" />
</bean>
<util:properties id="jpaProperties">
<prop key="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
</util:properties>
<bean id="daoService" class="org.esupportail.example.dao.JPADaoServiceImpl"
lazy-init="true">
</bean>
|
Explications :
- Le beanorg.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor demande à Spring de rechercher les classes java contenant des annotations définissant la façon d'enregistrer les objets en base de données.
- Le bean entityManagerFactory permet la création du gestionnaire d'entités. Il a comme propriétés :
- dataSource qui est la source de donnée à utiliser. Ici on utilise une variable Spring qui permettra de pointer vers un bean définissant une source de données JDBC (JDBCDataSource) ou JNDI (JNDIDataSource, gérée par le serveur d'applications)
- jpaVendorAdapter qui précise l'implémentation JPA utilisée.
- PersistenceXmlLocation qui permet de donner le nom du fichier de configuration JPA à utiliser (/properties/dao/persistence.xml). Ce dernier donne le nom du gestionnaire d'entités et les classes qui doit gérer.
- JpaProperties qui pointe vers un bean permettant de donner des informations générales liées au comportement de JPA.
- Le bean JDBCDataSource définit localement un poll de connexions à la base de données.
- Le bean JNDIDataSource permet de pointer vers un poll de connexions à la base de données géré par le serveur d'applications. Il a comme propriétés :
- jndi-name qui correspond au nom du poll dans la configuration JNDI du serveur d'applications. Ici on utilise unevariable Spring.
- lookup-on-startup="false" permet de préciser que l'on ne fera pas la recherche JNDI du poll de connexions au démarrage de l'application. C'est utile car l'on utilise une variable Spring pour savoir si on doit utiliser une source de données JDBC ou JNDI. Aussi il ne faut pas activer systématiquement la recherche alors que la configuration n'y fera peut-être pas référence.
- Le bean jpaProperties permet de donner des informations générales liées au comportement de JPA.
- Le bean daoService permet de définir la classe qui, dans notre application, contiendra les méthodes d'accès aux données.
Utilisation de plusieurs gestionnaires d'entités.
Si on souhaite accéder à n bases de données il est possible de déclarer n gestionnaires d'entités.
| Avertissement |
|---|
TODO |
Mapping avec la base de données
Le fichier de configuration JPA (/properties/dao/persistence.xml) contient la liste des classes dont il doit assurer la persistance.
En JPA il est possible de définir se mapping directement sous forme d'annotations dans le code java des classes à persister. C'est la solution préconisée dans le cadre de ESUP-Commons V2.
Pour avoir une information complète sur ces annotations Cf. http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html
Voici un extrait de la classe User :
| Bloc de code |
|---|
@Entity
public class User implements Serializable {
/**
* Id of the user.
*/
@Id
private String id;
/**
* Display Name of the user.
*/
private String displayName;
/**
* True for administrators.
*/
private boolean admin;
/**
* The prefered language.
*/
private String language;
/**
* information recorded during database insert
* used in esup-example to illustrate open session in view mechanism
*/
@OneToMany(cascade={CascadeType.ALL})
private List<Information> informations;
|
Explications :
- L'annotation @Entity précise que la Classe est une entité à persister. Par défaut tous les propriétés de la classe seront persistées. Un règle de nommage par défaut (si pas de présence d'une annotation sur la propriété pour une déclaration explicite) sera appliquée afin de déterminer le nom de la colonne en base de données.
- L'annotation @Id précise que cette propriété sera utilisée comme clé primaire de la table dans la base de données.
- Note : En JPA comme en Hibernate il est recommandé d'utiliser une clé physique (celle qui sera utilisée comme clé primaire de la table dans la base de données) différente de la clé métier utilisé par l'application. Il est aussi recommandé de créer des méthodes equals et hcode basée sur la clé métier.
Ici User est un cas particulier où la clé physique est aussi la clé métier dans la mesure où la clé n'est pas calculée (non utilisation de l'annotation @GeneratedValue en plus de*@Id*). Néanmoins cette pratique reste déconseillée car si nous devions faire évoluer notre besoin métier (par exemple utiliser une autre propriété de la classe -en plus de id- en tant que clé métier) nous n'arriverions pas à le faire (la contrainte d'unicité physique portant exclusivement sur id)
- Note : En JPA comme en Hibernate il est recommandé d'utiliser une clé physique (celle qui sera utilisée comme clé primaire de la table dans la base de données) différente de la clé métier utilisé par l'application. Il est aussi recommandé de créer des méthodes equals et hcode basée sur la clé métier.
- L'annotation
défini une association un à plusieurs. La cascade permet de préciser que si on fait une opération, typiquement un effacement, sur le père alors elle sera répercutée sur les enfants.Pas de format @OneToMany(cascade={CascadeType.ALL})
Utilisation de EJB QL
EJB QL
La déclaration d'un gestionnaire de base de données dont l'application n'a pas la maîtrise se fera de la manière suivante :
| Bloc de code |
|---|
<bean
id="databaseManager2"
class="[...].hibernate.BasicHibernateDatabaseManagerImpl" >
<property
name="sessionFactoryBeanName"
value="sessionFactory" />
</bean>
|
Les session factories (« usines à session »)
Les trois beans sessionFactory, createSessionFactory et updateSessionFactory (le premier seulement si l'on ne maîtrise pas la structure de la base de données) héritent tous du même bean abstrait abstractHibernateSessionfactory :
| Bloc de code |
|---|
<bean
id="abstractHibernateSessionFactory"
abstract="true"
class="[...].orm.hibernate3.LocalSessionFactoryBean" >
<property
name="configLocation"
value="classpath:/properties/dao/hibernate/hibernate.cfg.xml" />
<property name="mappingLocations">
<list>
<value>
classpath:/properties/dao/hibernate/mapping/Class1.hbm.xml
</value>
...
</list>
</property>
</bean>
|
Cette déclaration indique que la configuration de l'accès physique à la base de données se trouve dans le fichier /properties/dao/hibernate/hibernate.cfg.xml, et que les mappings des classes se trouvent dans le répertoire /properties/dao/hibernate/mapping.
Le lecteur se reportera à la documentation Hibernate en ce qui concerne le contenu du fichier /properties/dao/hibernate/hibernate.cfg.xml.
Les beans sessionFactory, createSessionFactory et updateSessionFactory précisent simplement leur mode d'accès particulier (la propriété hibernateProperties surcharge les valeurs données dans le fichier /properties/dao/hibernate/hibernate.cfg.xml).
| Bloc de code |
|---|
<bean id="sessionFactory"
parent="abstractHibernateSessionFactory"
lazy-init="true" >
</bean>
<bean id="createSessionFactory"
parent="abstractHibernateSessionFactory"
lazy-init="true" >
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
</bean>
<bean id="updateSessionFactory"
parent="abstractHibernateSessionFactory"
lazy-init="true" >
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
|
Les trois modes de Hibernate sont utilisés par esup-commons :
- L'accès normal à la base de données s'appuie sur le bean sessionFactory (accès web et accès batch),
- La création se fait en instanciant le bean createSessionFactory (via la tâche ant init-data),
- La mise à jour se fait en instanciant le bean updateSessionFactory (via la tâche ant upgrade).
Mapping avec la base de données
Les fichiers de mapping sont dans le répertoire /properties/dao/hibernate/mapping et portent, par convention, l'extension .hbm.xml.
Voici ici l'exemple d'une classe Entry, qui appartient au package org.esupportail.formation.domain et a comme attributs id, value et date (de types long, java.lang.String et java.sql.Timestamp) :
| Bloc de code |
|---|
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="org.esupportail.formation.domain.beans">
<class name="Entry" table="d_entry">
<id name="id" type="long">
<column name="id"/>
<generator class="native" />
</id>
<property name="value" type="string">
<column name="valu" length="250"
not-null="true" unique="true"/>
</property>
<property name="date" type="timestamp">
<column name="dat" not-null="true"/>
</property>
</class>
</hibernate-mapping>
|
Dans l'exemple, id est la clé primaire dans la table. id est dissocié de value qui a aussi une contrainte d'unicité dans la base de données. C'est une des bonnes pratiques pour Hibernate que de ne donner aucune notion métier à la clé primaire en base pour garantir l'évolutivité de l'application.
Si le schéma de votre base de données est déjà défini, vous pouvez générer automatiquement avec Eclipse les fichiers de mapping Hibernate et les POJOs (Plain Old Java Objects) dont vous avez besoin. Cette opération vous fera gagner énormément de temps de développement et d'erreurs.
Pour cela, dans Eclipse, après avoir correctement paramétré le fichier hibernate.cfg.xml, faire Window > Open Perspective.
Dans la vue Hibernate Configurations de cette perspective, cliquer sur le bouton "+" pour définir une nouvelle configuration. Renseigner les champs en s'inspirant l'exemple suivant :
Pour initialiser la génération des fichiers Hibernate, faire Run > Hibernate Code Generation... > Open Hibernate Code Generation... Dialog... et renseigner à nouveau les champs en s'inspirant l'exemple suivant :
Il ne vous reste plus qu'à cliquer sur le bouton Run.
Vous trouverez tous les fichiers générés dans le répertoire indiqué dans le champ Output Directory de l'onglet Main (cf ci-dessus). N'oubliez pas quand même :
- de déplacer les fichiers de mappings Hibernate générés (fichiers *.hbm.xml) dans le répertoire esup-blank/properties/dao/hibernate/mapping !
- de rajouter dans le fichier dao.xml chaque fichier de mapping généré en ajoutant, par exemple pour une entité "Population", la ligne :
<value>classpath:/properties/dao/hibernate/mapping/Population.hbm.xml</value>
Une fois ce travail effectué, vous pouvez directement lancer la target init-data du fichier build-devel.xml !
Utilisation de HQL
HQL est un langage d'interrogation de base de données de Hibernate JPA. Il est orienté objet et a une forme proche du SQL mais travaille sur les objets Java définis dans vos fichiers de mapping et pas sur des noms de tables de votre base de données.
Note : Hibernate propose aussi une autre méthode d'interrogation, dite « requêtes par critères ». Il s'agit d'une API et plus d'un langage comme HQL. Cette dernière n'est pas abordée dans ce document.
Contrairement à SQL, le mot clé SELECT de HQL n'est pas obligatoire. S'il n'est pas utilisé ce sont des objets qui sont retournés. S'il est utilisé, il est possible de seulement retourner les propriétés de ces objets.
Après le mot-clé FROM, on n'utilise pas de des noms de tables mais des noms de classes. Ce nom de classe est sensible à la casse comme en Java. Le nom du package n'est pas obligatoire car Hibernate a un mécanisme d'auto-import.
Exemple de requête HQL simple qui d'une requête EJB QL simple qui récupère toutes les instances de la classe Thing dans User de la base de données données :
| Bloc de code |
|---|
"SELECT user FROM User Thinguser" |
Avec HQL il est possible de faire des jointures (mot clé JOIN), d'utiliser le mot clé WHERE afin de limiter les objets à retourner par la requête. Il est aussi possible d'utiliser des fonctions d'agrégation (sum(...), count( * )) et des expressions (upper(), +, between) dans les requêtes HQL. De même, le support de sous-requêtes et des clauses ORDER BY et GROUP BY est offert.
Pour plus de renseignements sur la construction des requêtes HQL, le lecteur se reportera à la documentation de Hibernate.
Dans esup-commons HQL est notamment utilisé pour :
- Compter le nombre d'instances persistantes d'une classe (nombre de lignes dans une table) avec la méthode getQueryIntResult. Par exemple :
Bloc de code getQueryIntResult("select count(*) from Entry"); - Sélectionner les objets à faire apparaître dans un paginateur Hibernate.
Parmi les autres recommandations pour Hibernate on trouvera le fait de devoir surcharger les méthodes hashCode() et equals(), par exemple :
| Bloc de code |
|---|
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof Entry)) {
return false;
}
return id == ((Entry) obj).getId();
}
public int hashCode() {
return super.hashCode();
}
|
...
Écrire la classe Entry. Ecrire le fichier de mapping correspondant et le référencer depuis dao.xml. Exécuter la tâche ant init-data et vérifier que la table correspondante a bien été créée dans la base de données.
| solution | ||||||
|---|---|---|---|---|---|---|
Créer le fichier Entry.hbm.xml en prenant modèle sur une fichier existant :
Ajouter la référence au fichier Entry.hbm.xml dans le fichier properties/dao/dao.xml
Ecrire ensuite la classe Entry dans le package xxx.domain.beans :
|
Comment ça marche
Comme vu plus haut, les points d'entrée de esup-commons s'assurent du respect du modèle one-session-per-request.
Lors de l'ouverture d'une session Hibernate à une base de données, on appelle le code suivant :
| Bloc de code |
|---|
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
|
Cela associe la session créée (session) à l'usine à sessions (sessionFactory) qui l'a créée, pour le thread courant.
Pour accéder aux données, le service d'accès aux données HibernateDaoService utilise la méthode getHibernateTemplate() (héritée de la classe HibernateDaoSupport). Cette méthode getHibernateTemplate() récupère la session courante dans les données du thread courant à partir de l'usine à session (sessionFactory), injectée dans le bean daoService par Spring.
Ce mécanisme ingénieux permet d'éviter toute adhérence entre le service métier et le service d'accès aux données. La classe statique DatabaseUtils permet de réduire toute adhérence entre les points d'entrée de esup-commons et les gestionnaires de données utilisés. Pour une information complète sur l'utilisation de EJB QL Cf. http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/EJBQL.html




