Il faut commencer par créer un [[ide:eclipse:projet#projet_jpa|projet JPA]] et ensuite configurer le fichier ''persistence.xml''.
=====Cycle de vie=====
{{ :lang:java:jpa:cycle_de_vie_jpa.svg |}}
[[https://armahdian.wordpress.com/2010/11/18/%D9%85%D8%B9%D8%B1%D9%81%DB%8C-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%DB%8C-java-ee-%D9%88-ejb-3/|معرفی معماری Java EE و EJB 3 _ آموزش برنامه نویسی]] {{ :lang:java:jpa:معرفی_معماری_java_ee_و_ejb_3_آموزش_برنامه_نویسی_2020-04-26_8_29_44_pm_.html |Archive du 18/11/2010 le 26/04/2020}}
=====persistence.xml=====
If an entity instance is to be passed by value as a detached object (e.g., through a remote interface), the entity class must implement the ''Serializable'' interface.[[https://jcp.org/en/jsr/detail?id=338|JSR 338: Java TM Persistence API, Version 2.1]], {{ :lang:java:jpa:javapersistence2.1.pdf |Archive}}La classe ne doit pas être finale et doit avoir un constructeur sans argument explicitement écrit avec une visibilité publique ou protégée. L'une des quatre annotation est nécessaire ''@Stateful'', ''@Stateless'', ''@MessageDriven'' ou ''@Entity''. ===@Stateful=== Les beans de type ''Stateful'' sont capables de conserver leur état. ===@Stateless=== Les beans de type ''stateless'' n'ont par leur état conservé. ===@MessageDriven=== Les beans de type ''MessageDriven'' permettent de réaliser des traitements asynchrones exécutés à la réception d'un message dans une queue JMS. ===@Entity=== Les beans de type ''Entity'' utilisent la persistance (''JPA''). Par opposition aux composants (''@Embeddable'') qui sont des objets sans identifiant qui nécessitent d'être attachés à une entité pour être sauvegardés. ===@Embeddable=== Pour qu'une classe puisse être embarquée dans une entité, il faut que la classe : * soit déclarée ''@Embeddable'', * n'est pas d'attribut ''@Id''. ===@AttributeOverrides et @AttributeOverride=== Si on souhaite utiliser deux fois la même classe ''@Embeddable'', il va y avoir un problème puisque les attributs seront stockées dans la même colonne. Il faut donc utiliser ''@AttributeOverride'' qui dire qu'on souhaite remplacer un attribut précédemment défini par un autre.
@Embedded
@AttributeOverrides( {
// adresse est le nom de l'attribut de la classe ''@Embeddable''.
@AttributeOverride(name="adresse", column = @Column(name="adresse_pro") ),
@AttributeOverride(name="codePostal", column = @Column(name="code_postal_pro") ),
@AttributeOverride(name="ville", column = @Column(name="ville_pro") )
}
)
===@Table===
@Table(name="tbl_sky")
Défini le nom de la table si elle est (ou doit être) différente du nom de la classe.
Il est possible d'y ajouter des contraintes d'unicité à l'intérieur de la table :
@Table(name="EMPLOYE",
uniqueConstraints={
@UniqueConstraint(columnNames={"nom", "prenom"})
})
===@Access===
Indique la manière dont l%%'%%''entity manager'' va lire et modifier les attributs : directement avec l'attribut (''AccessType.FIELD'') ou en passant par les getter/setter (''AccessType.PROPERTY'').
===@Inheritance===
Dans le cas d'une entité qui est héritée, il est possible de dire si les données de l'entité parent doit être dans la même table que l'entité enfant.
@Inheritance(strategy=InheritanceType.JOINED)
* ''TABLE_PER_CLASS'' : une table par entité parents et enfants. Pas de jointure mais redondance des informations puisque les données de chaque enfant sont enregistrées dans,
* ''JOINED'' : une table pour l'entité parent et une pour chaque entité enfant. Une jointure sera effectuée pour avoir la classe entière,
* ''SINGLE_TABLE'' : une table pour toutes les entités (parent et enfants réunis). Aucune jointure n'est effectuée. On lit la bonne table directement sans jointure.
''@DiscriminatorColumn'' ou ''@PrimaryKeyJoinColumn'' est la colonne utilisée comme discriminant pour un héritage de type ''SINGLE_TABLE'' et ''JOINED''. Si les deux sont absents, la valeur par défaut est :
@DiscriminatorColumn(name="DTYPE", discriminatorType=DiscriminatorType.STRING)
''@DiscriminatorValue'' permet à chaque entité concrète implémentant une entité possédant une ''@DiscriminatorColumn'' de choisir le nom du discriminant. S'il est absent, la valeur par défaut est le nom de la classe si la colonne est de type ''STRING'' et un nombre généré sinon.
Exemple :
Soit la classe classe parent :
InheritanceType.JOINED
Video
CREATE TABLE `Video` (
`idVideo` bigint(20) NOT NULL AUTO_INCREMENT,
`annee` int(11) DEFAULT NULL,
`titre` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`idVideo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
FilmV
CREATE TABLE `FilmV` (
`id_video` bigint(20) NOT NULL,
`genre_code` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`pays_code` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`id_realisateur` int(11) DEFAULT NULL,
PRIMARY KEY (`id_video`),
KEY `FK_oimyaomgm9k6d10g06cxbp0yx` (`id_realisateur`),
CONSTRAINT `FK_oimyaomgm9k6d10g06cxbp0yx` FOREIGN KEY (`id_realisateur`) REFERENCES `Artiste` (`id`),
CONSTRAINT `FK_olwd2n0y4pblyxrqm26ylknpq` FOREIGN KEY (`id_video`) REFERENCES `Video` (`idVideo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Reportage
CREATE TABLE `Reportage` (
`lieu` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`id_video` bigint(20) NOT NULL,
PRIMARY KEY (`id_video`),
CONSTRAINT `FK_q6ri2c7u6yysm923kor5hbith` FOREIGN KEY (`id_video`) REFERENCES `Video` (`idVideo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
InheritanceType.SINGLE_TABLE
Video
CREATE TABLE `Video` (
`DTYPE` varchar(31) COLLATE utf8mb4_unicode_ci NOT NULL,
`idVideo` bigint(20) NOT NULL AUTO_INCREMENT,
`annee` int(11) DEFAULT NULL,
`titre` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`lieu` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`genre_code` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`pays_code` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`id_realisateur` int(11) DEFAULT NULL,
PRIMARY KEY (`idVideo`),
KEY `FK_a12jw76dvr55b0p8013hr6dli` (`id_realisateur`),
CONSTRAINT `FK_a12jw76dvr55b0p8013hr6dli` FOREIGN KEY (`id_realisateur`) REFERENCES `Artiste` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
InheritanceType.TABLE_PER_CLASS
Video
CREATE TABLE `Video` (
`idVideo` bigint(20) NOT NULL AUTO_INCREMENT,
`annee` int(11) DEFAULT NULL,
`titre` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`idVideo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
FilmV
CREATE TABLE `FilmV` (
`idVideo` bigint(20) NOT NULL,
`annee` int(11) DEFAULT NULL,
`titre` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`genre_code` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`pays_code` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`id_realisateur` int(11) DEFAULT NULL,
PRIMARY KEY (`idVideo`),
KEY `FK_oimyaomgm9k6d10g06cxbp0yx` (`id_realisateur`),
CONSTRAINT `FK_oimyaomgm9k6d10g06cxbp0yx` FOREIGN KEY (`id_realisateur`) REFERENCES `Artiste` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Reportage
CREATE TABLE `Reportage` (
`idVideo` bigint(20) NOT NULL,
`annee` int(11) DEFAULT NULL,
`titre` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`lieu` varchar(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`idVideo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
====Les attributs====
===@Id===
Indique la clé primaire de la classe. Si elle est mise au dessus d'un attribut, la lecture/écriture de tous les attributs de la classe se fera directement sur les attributs. Si elle est mise au dessus d'un getter/setter, la lecture/écriture de tous les attributs de la classe se fera par l'intermédiaire des getter/setter. Je n'ai pas trouvé où c'est indiqué précisément dans la PJA et apparemment, je ne suis pas le seul : [[https://stackoverflow.com/questions/18441222/issue-with-jpa-mapping-for-two-nested-embeddable|Issue with JPA mapping for two nested @Embeddable]] {{ :lang:java:jpa:hibernate_-_issue_with_jpa_mapping_for_two_nested_embeddable_-_stack_overflow_2020-04-26_8_53_29_pm_.html |Archive du 26/08/2013 le 26/04/2020}}.
Il est conseillé de mettre le setter de ''@Id'' en ''private''.
Par défaut, la propriété ''equals'' (ou opérateur ''=='') se détermine en regardant l'égalité de la référence.
L%%'%%''Id'' n'est pas suffisant pour déterminer que deux objets sont identique. Si on prend deux objets ayant un ''Id'' autogénérés, leurs ''id'' seront identiques à ''null'' avant l'insertion dans la base de données via ''save/commit''. Il faut donc, soit utiliser l'ensemble de la clé primaire (si attribut multiple et non auto-généré), soit utiliser les autres champs en prenant le risque d'une fausse égalité.
Ci-dessous deux exemples d'implémentation de la méthode ''equals'' et ''hashCode'' :
* Solution 1 :
public class Adresse {
private String adresse;
private String ville;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((adresse == null) ? 0 : adresse.hashCode());
result = prime * result + ((ville == null) ? 0 : ville.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Adresse other = (Adresse) obj;
if (adresse == null) {
if (other.adresse != null)
return false;
} else if (!adresse.equals(other.adresse))
return false;
if (ville == null) {
if (other.ville != null)
return false;
} else if (!ville.equals(other.ville))
return false;
return true;
}
}
* Solution 2 avec [[https://commons.apache.org/proper/commons-lang/|commons-lang3 de Apache]] {{ :lang:java:commons-lang3-3.5-bin.tar.gz |Archive}} :
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder(5381, 33);
hcb.append(adresse);
hcb.append(ville);
return hcb.toHashCode();
}
@Override
public boolean equals(Object obj) {
// Partie générée automatiquement par Eclipse.
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Adresse other = (Adresse) obj;
EqualsBuilder eb = new EqualsBuilder();
// append utilise la méthode equals.
eb.append(adresse, other.adresse);
eb.append(ville, other.ville);
return eb.isEquals();
}
===@GeneratedValue===
En complément à ''@Id'', il est possible d'indiquer que la génération de l'Id doit être automatique.
* ''strategy = GenerationType.AUTO'' : le type de génération est laissé à la discrétion de l'implémentation de PJA. Valeur par défaut.
* ''strategy = GenerationType.IDENTITY'' : auto-incrémentation d'un numéro de façon traditionnelle.
* ''strategy = GenerationType.SEQUENCE'' : si j'ai bien compris, l'intérêt est pour un accès concurrent à la base de données. Dès qu'une application génère un objet, il ne prend pas qu'une seule valeur mais en réserve une certaine quantité. Ainsi, l'application suivante pourra utiliser la plage de données suivante. L'intérêt est de permettre au SGBD d'optimiser les requêtes et ainsi permettre l'ajout différé des objets. Cela ne semble être compatible que Oracle et PostgreSQL [[https://thoughts-on-java.org/jpa-generate-primary-keys/|How to generate primary keys with JPA and Hibernate]] {{ :lang:java:jpa:how_to_generate_primary_keys_with_jpa_and_hibernate_2020-04-26_9_01_29_pm_.html |Archive du 2016 le 26/04/2020}}.
* ''strategy = GenerationType.TABLE'' : Dans les autres cas, cela passe par l'utilisation d'une table supplémentaire ''hibernate_sequence'' et l'ajout d'un élément nécessite alors une opération de lecture préalable dans cette table.
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_generator")
@SequenceGenerator(name="book_generator", sequenceName = "book_seq", initialValue=100, allocationSize=50)
===@Transient===
Par défaut, tous les attributs sont sauvegardés dans la base de données. Pour qu'un attribut ne soit pas sauvegardé, il faut lui ajouter l'annotation ''@Transient''.
===@Column===
Permet de spécifier les caractéristiques de la colonne de la base de données, uniquement pour des colonnes n'étant pas des jointures (pour ''@ManyToOne'' il faut utiliser ''@JoinColumn'').
Seul, il ne sert à rien puisque de toute façon, tous les attributs / getter/setter sont automatiquement persistants. L'objectif est de pouvoir définir :
^Option^Description^Valeur par défaut^
|''name = %%"XXXX"%%''|Le nom de la colonne|Nom de la propriété ou de l'attribut.|
|''unique = true''|Si la colonne est composée de clés uniques.|''false''|
|''nullable = true''|Si les valeurs null sont acceptées.|''true''|
|''length = 32''|Nombre de caractères d'une colonne de type ''String'' uniquement.|255|
|''insertable = true'' \\ ''updatable = true''|Autorise ou non les requêtes INSERT et UPDATE.|true|
* etc…
===@Embedded, @EmbeddedId===
Lorsqu'un attribut fait référence à un objet, ''@Embedded'' permet de dire que l'objet doit être intégré dans la classe en cours et ne pas faire l'objet d'un table à part. ''@EmbeddedId'' fait que tous les attributs de l'objet intégré composent la clé primaire de la classe en cours.
L'objet à embarquer doit être déclaré ''@Embeddable''
@Version
private Long version;
====Association/mapping====
[[http://blog.paumard.org/cours/jpa/chap03-entite-relation.html|Mise en relation d'entités]] {{ :lang:java:jpa:4._mise_en_relation_d_entites_2020-04-26_9_15_26_pm_.html |Archive du 2012 le 26/04/2020}}
===@OneToOne===
Association entre deux instances lorsque l'instance A ne peut appartenir qu'à l'instance B et que l'instance B ne peut appartenir qu'à l'instance A. Par exemple une personne et sa carte d'identité ou encore une personne et son adresse principale.
Paramètres :
* ''mappedBy'' : est utilisé dans une relation bidirectionnelle.
Soit une personne et sa carte d'identité. La personne possède une carte d'identité via un attribut de type ''CarteDIdentite'' et un mapping ''OneToOne''.
Si on souhaite connaître la personne depuis la carte d'identité, il faut ajouter un champ ''Personne'' dans ''CarteDIdentite'' et un mapping ''OneToOne'' avec comme paramètre ''mappedBy=XXX'' avec ''XXX'' le nom de l'attribut de type ''CarteDIdentite'' dans la classe ''Personne''.
@Entity
public class Client implements Serializable {
@OneToMany(mappedBy="client")
private List comptes;
}
Dans le cas d'une relation bidirectionnelle, c'est dans ''@OneToMany'' qu'il faut utiliser le paramètre ''@mappedBy'', pas dans ''@ManyToOne''.
===@ManyToOne===
Plusieurs instances de la classe font référence à une seule autre classe (mais pas forcément la même instance). Association uni-directionnelle. Il faut consulter la première classe pour connaître la deuxième.
Plusieurs marins peuvent travailler dans un même bateau. Il faut connaître le marin pour savoir dans quel bateau il travaille.
@Entity
public class Marin implements Serializable {
@ManyToOne
private Bateau bateau ;
}
Plusieurs films peuvent être tourné dans un même pays. Il faut connaître la fiche du film pour savoir dans quel pays il a été tourné.
@Entity
public class Film implements Serializable {
@ManyToOne
private Pays pays ;
}
Dans le cas d'une relation bidirectionnelle, c'est dans ''@ManyToOne'' qu'il faut utiliser ''@JoinColumn'' (qui contiendra le nom de la colonne) et dans ''@OneToMany'' qu'il faut utiliser ''mappedBy'' (qui contiendra le nom de l'attribut).
Il est théoriquement possible d'utiliser le même ''@JoinColumn'' avec les mêmes paramètres du coté ''@OneToMany'' mais il va en résulter une requête SQL de type ''update'' qui serait inutile.
===@ManyToMany===
Une instance de la classe A peut appartenir à plusieurs instances de la classe B et une instance de la classe B peut appartenir à plusieurs instances de la classe A.
Sur un site d'e-commerce, un objet peut être dans le panier de plusieurs personnes et un panier peut posséder plusieurs objets.
Une relation bidirectionnelle de type ''@ManyToMany'' passe toujours par une table de jointure.
Il est possible de la déclarer explicitement avec ''@JoinTable'' par la syntaxe suivante :
* ''name'' est le nom de la table SQL,
* ''joinColumns'' représente le nom de la colonne qui contient l%%'%%''@id'' de la classe en cours,
* ''inverseJoinColumns'' représente le nom de la colonne qui contient l%%'%%''@id'' de la classe opposée,
@ManyToMany
@JoinTable(name = "Role", joinColumns = @JoinColumn(name = "id_film"),
inverseJoinColumns = @JoinColumn(name = "id_acteur"))
Dans la classe opposé, on utilise juste ''mappedBy'' qui contient l'attribut de la classe principale.
@ManyToMany(mappedBy = "acteur")
===Commun aux ''@OneTo''/''@ManyTo''===
* ''cascade=CascadeType.ALL'' : active toutes les fonctionnalités possibles de la cascade en SQL. Quand une entité est persistée (''EclipseLink'' : ''persiste'', ''Hibernate'' : ''save''), les attributs ayant une cascade à ''PERSIST'' sont automatiquement persistés également.
* ''fetch=Fetchtype.LAZY'' : indique si la liste d'entités doit être chargé lors de la création de la classe (''EAGER'') ou uniquement lors de la lecture de la liste (''LAZY'').
Les valeurs théoriques par défaut (''Hibernate 5.0'' pas avant) sont : ''@OneToMany'' : ''LAZY'', ''@ManyToOne'' : ''EAGER'', ''@ManyToMany'' : ''LAZY'', ''@OneToOne'' : ''EAGER''.
Dans le cas où plusieurs éléments d'une liste doit être chargée, il peut être plus efficace d'effectuer une requête permettant de charger automatiquement l'élément en cours et quelques suivants avec ''@BatchSize''. Par contre, durant mes essais, je n'ai réussi à faire une requête multique qu'en mode ''EAGER'' et non ''LAZY''.
@OneToMany(fetch=FetchType.EAGER)
@BatchSize(size=10)
private Set notations = new HashSet();
===@JoinColumn===
''@Column'' permet de nommer le nom de la colonne d'un attribut, ''@JoinColumn'' (attribut ''name'') permet de nommer le nom d'une colonne d'une association. C'est dans cette table que sera la clé étrangère.
Cette annotation s'utilise du coté de l'entité propriétaire de la relation. Pour faire une liaison bidirectionnelle, il faut utiliser le paramètre ''mappedBy'' dans l'annotation de mapping de l'autre entité.
@JoinColumn(name = "code_pays")
* ''referencedColumnName = %%"XX"%%'' : définir de façon explicite l'attribut de l'autre classe qu'il faut utiliser dans la liaison avec ''XX'' le nom de la colonne dans la base de données de l'attribut de l'autre classe.
====Bases de données réparties====
===Une entité dans plusieurs tables===
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
// Dans SecondaryTable, name correspond au nom de la table et
// pkJoinColumns permet de définir les colonnes de jointure.
// Si deux attributs composent la clé primaire, il est possible
// de définir la valeur de pkJoinColumns sous forme d'un tableau.
@Entity
@SecondaryTables({
@SecondaryTable(name="PERSO_SECU", pkJoinColumns=@PrimaryKeyJoinColumn(referencedColumnName="ID")),
@SecondaryTable(name="PERSO_BANQUE", pkJoinColumns=@PrimaryKeyJoinColumn(referencedColumnName="ID"))
})
public class Personne {
@Id
private int id;
private String nom;
private String prenom;
// Il faut définir explicitement dans quelle table est cet attribut et que
// le nom de la table est dans une @SecondaryTable.
@Column(table="PERSO_SECU")
private Long numSecu;
@Column(table="PERSO_BANQUE")
private int compteEnBanque;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public Long getNumSecu() {
return numSecu;
}
public void setNumSecu(Long numSecu) {
this.numSecu = numSecu;
}
public int getCompteEnBanque() {
return compteEnBanque;
}
public void setCompteEnBanque(int compteEnBanque) {
this.compteEnBanque = compteEnBanque;
}
}
=====Exemples=====
====Les Bean====
Session session = new Configuration().configure().buildSessionFactory().openSession();
EclipseLink :
EntityManager emf = Persistence.createEntityManagerFactory("XXX").createEntityManager();
avec "XXX" le même nom du projet défini dans ''persistence.xml'' : ''
Pays monPays = new Pays();
monPays.setCode("IS");
monPays.setNom("Islande");
monPays.setLangue("Islandais");
Hibernate :
session.beginTransaction();
session.save(monPays);
session.getTransaction().commit();
EclipseLink :
em.getTransaction().begin();
em.persist(entite);
em.getTransaction().commit();
* ''persist'' enregistre ''entite'' dans la base de données et fait que les données de ''entite'' dans la base de données seront mises à jour si les attributs sont modifiés.
* ''remove'' a pour effet de rendre une entité non persistante.
* ''refresh'' a pour effet de synchroniser l’état de l’entité en mémoire avec ses valeurs présentes en base de données. Nécessaire pour synchroniser un objet après un ''merge''.
* ''detach'' a pour effet de détacher l’entité considérée de l’entity manager qui la gère, et, dès lors, les valeurs qu’on pourra affecter à cette entité ne seront pas prises en compte au prochain commit. Une telle instance reste persistante, et pourra être ensuite, par exemple, attachée à un autre entity manager).
* ''merge'' enregistre ''entite'' dans la base de données mais les données de ''entite'' dans la base de données ne seront PAS mises à jour si les attributs sont modifiés.
{{ :lang:java:jpa:entity_manager.svg |}}
===Gestion des flux===
* ''close()'' : exécute toutes les requêtes SQL en attente puis ferme la connexion avec la base de données.
* ''clear()'' : annule toutes les requêtes SQL en attente.
* ''flush()'' : exécute toutes les requêtes SQL en attente.
===Lecture===
==Tous==
* API Criteria :
Hibernate 5.2
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery criteria = builder.createQuery(Pays.class);
List resultat = session.createQuery(criteria).getResultList();
List results = session.createCriteria(Item.class)
.add(Expression.eq("item.seller", user))
.setFetchMode("bids", FetchMode.EAGER).list();
Iterator items = new HashSet(results).iterator();
while (items.hasNext())
{
...
}
Trouve en fonction de la clé primaire.
Employe employe = em.find(Employe.class, 5233);
* HQL / JPQL
Le ''HQL'' est la version spécifique du ''JPQL'' pour ''Hibernate''.
Version simple
import org.hibernate.query.Query;
Query query = session.createQuery("from Pays", Pays.class);
List resultat = query.getResultList();
Avec un where
Query query = session.createQuery("from Pays p where p.code = :code", Pays.class);
query.setString("code", "FR");
List resultat = query.getResultList();
On notera ici la présence de '':code'' et la définition de sa valeur par ''query.setString("code", FR)''.
Passer par des ''query'' avec des ''select'' permet d'éviter que le cache de premier niveau de ''Hibernate'' ne soit remplit.
La syntaxe est proche de ''SQL''.
Cela marche aussi avec des références.
Artiste bergman = …;
…
Query query = session.createQuery("from FilmV f where f.realisateur = :rel", FilmV.class);
query.setString("rel", bergman);
List resultat = query.getResultList();
Exemple plus complet :
select employe
from Employe employe
where prenom = 'Pierre'
On constate que ''from'' est une classe, pas une table. ''select'' renvoie des instances, pas des colonnes.
JPQL s'occupe de faire les jointures automatiques (''deptAffectation'' est une liaison ''ManyToOne'') :
select employe.deptAffectation
from Employe employe
where employe.prenom = 'Pierre'
est identique à
select departement
from Employe employe join employe.deptAffectation departement
where employe.prenom = 'Pierre'
On peut aussi forcer le préchargement (''fetch'') des données des tables jointes.
select employe
from Employe employe left join fetch employe.deptAffectation
where prenom = 'Pierre'
select titre from Film f where f.realisateur.nom = 'Eastwood'
select
film0_.titre as col_0_0_
from
Film film0_ cross
join
Artiste artiste1_
where
film0_.id_realisateur=artiste1_.id
and artiste1_.nom='Eastwood'
select film.titre from Film as film join film.realisateur as a where a.nom='Eastwood'
select
film0_.titre as col_0_0_
from
Film film0_
inner join
Artiste artiste1_
on film0_.id_realisateur=artiste1_.id
where
artiste1_.nom='Eastwood'
select film.titre from Film as film, Artiste as a where film.realisateur = a and a.nom='Eastwood'
select
film0_.titre as col_0_0_
from
Film film0_ cross
join
Artiste artiste1_
where
film0_.id_realisateur=artiste1_.id
and artiste1_.nom='Eastwood'
Il n'est pas possible de faire une recherche directement dans une liaison ''@ManyToMany'' ou ''@ManyToOne''. Dans ces cas, il faut passer par une jointure.
select distinct film.titre from Film as film
join film.roles as role join role.pk.acteur as acteur
where acteur.nom= 'Eastwood'
select
distinct film0_.titre as col_0_0_
from
Film film0_
inner join
Role roles1_
on film0_.id=roles1_.id_film
inner join
Artiste artiste2_
on roles1_.id_acteur=artiste2_.id
where
artiste2_.nom='Eastwood'
select distinct film.titre from Film as film
join film.roles as role
where role.pk.acteur.nom= 'Eastwood'
select
distinct film0_.titre as col_0_0_
from
Film film0_
inner join
Role roles1_
on film0_.id=roles1_.id_film cross
join
Artiste artiste2_
where
roles1_.id_acteur=artiste2_.id
and artiste2_.nom='Eastwood'
Pour mémo ''inner join'' renvoie un croisement entre deux bases de données en fonction d'un critère d'égalité. Dans le cas d'une relation automatique, pas besoin de ''on''. Pour ''left outer join'', la requête va renvoyer aussi lorsque aucune correspondance n'existe en remplaçant le champ manquant par la valeur ''null''.
Les sous-requête HQL marchent de façon proche des SQL :
select film.titrefrom Film as film where film in
(select role.pk.film from Role as role where role.nom='McClane')
select film.titre from Film as film where exists
(from Artiste as a where a = film.realisateur and a.nom='Eastwood')
from Film as film where 1940 > any
(select role.pk.acteur.anneeNaissance from Role as role where role.pk.film=film)
Et ''group by'' et ''having'' sont proches à SQL :
select film.titre, count(*) from Film as film join film.roles as role group by film order by 2 ASC
select artiste.nom, count(*) from Artiste as artiste join artiste.filmsRealises as film
group by artiste having count(*) > 3
Pour modifier le ''fetch'' lors de la lecture d'un objet, faire :
select film from Film as film join film.realisateur
where film.titre= :titre
select
film0_.id as id1_1_,
film0_.annee as annee2_1_,
film0_.genre as genre4_1_,
film0_.code_pays as code_pay5_1_,
film0_.id_realisateur as id_reali6_1_,
film0_.titre as titre3_1_
from
Film film0_
left outer join
Artiste artiste1_
on film0_.id_realisateur=artiste1_.id
where
film0_.titre='Vertigo'
n'est pas suffisant pour que le réalisateur soit chargé dans la classe ''Film'' si réalisateur à un fetch ''LAZY''. Il faut alors utiliser les mots clé ''join fetch''.
select film from Film as film left join fetch film.realisateur
where film.titre= :titre
select
film0_.id as id1_1_0_,
artiste1_.id as id1_0_1_,
film0_.annee as annee2_1_0_,
film0_.genre as genre4_1_0_,
film0_.code_pays as code_pay5_1_0_,
film0_.id_realisateur as id_reali6_1_0_,
film0_.titre as titre3_1_0_,
artiste1_.annee_naissance as annee_na2_0_1_,
artiste1_.nom as nom3_0_1_,
artiste1_.prenom as prenom4_0_1_
from
Film film0_
left outer join
Artiste artiste1_
on film0_.id_realisateur=artiste1_.id
where
film0_.titre='Vertigo'
Mais ce n'est pas encore parfait car si on fait une recherche avec un critère dans une liste :
select film
from Film as film
left join fetch film.roles as role
where role.nom= 'McClane'
select
film0_.id as id1_1_0_,
roles1_.id_acteur as id_acteu2_6_1_,
roles1_.id_film as id_film3_6_1_,
film0_.annee as annee2_1_0_,
film0_.genre as genre4_1_0_,
film0_.code_pays as code_pay5_1_0_,
film0_.id_realisateur as id_reali6_1_0_,
film0_.titre as titre3_1_0_,
roles1_.nom_role as nom_role1_6_1_,
roles1_.id_film as id_film3_6_0__,
roles1_.id_acteur as id_acteu2_6_0__
from
Film film0_
left outer join
Role roles1_
on film0_.id=roles1_.id_film
where
roles1_.nom_role='McClane'
il n'y a que le rôle qui correspond qui est chargé, pas tous les rôles. Pour avoir tous les rôles, il faut modifier la requête :
from Film as film
left join fetch film.roles as role
where film in (select r2.pk.film from Role as r2 where nom= 'McClane')
select
film0_.id as id1_1_0_,
roles1_.id_acteur as id_acteu2_6_1_,
roles1_.id_film as id_film3_6_1_,
film0_.annee as annee2_1_0_,
film0_.genre as genre4_1_0_,
film0_.code_pays as code_pay5_1_0_,
film0_.id_realisateur as id_reali6_1_0_,
film0_.titre as titre3_1_0_,
roles1_.nom_role as nom_role1_6_1_,
roles1_.id_film as id_film3_6_0__,
roles1_.id_acteur as id_acteu2_6_0__
from
Film film0_
left outer join
Role roles1_
on film0_.id=roles1_.id_film
where
film0_.id in (
select
role2_.id_film
from
Role role2_,
Film film3_
where
role2_.id_film=film3_.id
and role2_.nom_role='McClane'
)
Dans le cas où une transaction va modifier une valeur, il peut être intéressant de mettre ''for update'' dans la requête HQL pour imposer un verrou sur les éléments.
Dans le cas de la méthode ''get'', il faut ajouter le paramètre ''LockMode.UPGRADE''.
Réduire le nombre de résultats :
query.setFirstResult(10);
query.setMaxResults(10);
// Si un seul résultat (requête sur les attributs de la clé primaire / unique), il est possible de récupérer seulement l'instance.
query.uniqueResult(10);
* Hibernate
Ici, ''id'' doit être sérializable impérativement.
session.get(Film.class, id); // null en cas d'échec.
session.load(Film.class, id); // Exception en cas d'échec.
===Requête SQL===
EclipseLink :
List list = em.createQuery("select p from Pays p", Pays.class).getResultList();
ou
@Entity
@NamedQueries(value = {
@NamedQuery
(
resultClass=Commande.class,
name = "Pays.TousLesPays",
query = "select p from Pays p"
)
})
public class Pays implements Serializable {
…
}
List list = em.createNamedQuery("TousLesPays", Pays.class).getResultList();
Lors qu'une classe ne possèque qu'une seule requête nommée, ''@NamedQuery'' est suffisant. Sinon, il faut les ranger dans le paramètre ''value'' de ''@NamedQueries'' sous forme d'un tableau.
Il n'est pas obligatoire que le nom soit ''NomClasse.nomQuery'' mais cela permet de bien ranger les ''Query''.
====hibernate.cfg.xml====
''hibernate.connection.isolation = ''
* ''NONE'' ou 0,
* ''READ_COMMITTED'' ou 2,
* ''READ_UNCOMMITTED'' ou 1,
* ''REPEATABLE_READ'' ou 4,
* ''SERIALIZABLE'' ou 8,
=====Ressources=====
[[http://tahe.developpez.com/tutoriels-cours/jpa/|Persistance Java 5 par la pratique]], {{ :lang:java:jpa:jpa_developpez.pdf |Archive PDF}}, {{ :lang:java:jpa:jpa_aveclib.zip |Archive projet}}