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===== ''Hibernate'' semble ne pas supporter si les champs ''persistence'' sont dans un ''namespace''. ''EclipseLink'' a l'air de le supporter mais c'est ''Eclipse'' qui alors semble mal le prendre. ''exclude-unlisted-classes'' : exclut toutes les classes non explicitement ''@Entity''. ====EclipseLink==== false ''eclipselink.ddl-generation'' : * ''create-tables'' : créera les tables si elles n'existent pas. Si elles existent et qu'elles ne correspondent pas au profil de l'entité, une exception sera générée. * ''create-or-extend-tables'' : créera les tables sur elles n'existent pas et ajoutera les colonnes manquantes. * ''drop-and-create-tables'' : supprimer et recrée les tables à chaque lancement de l'application. ====Hibernate==== Pour hibernate, il est possible d'utiliser soit le fichier ''persistence.xml'' (automatiquement généré dans le dossier ''WEB-INF'' et accessible depuis ''Project Explorer'' dans la rubrique ''Java Resources''), soit ''hibernate.cfg.xml'' (à mettre dans le dossier ''WEB-INF/classes'', à créer si nécessaire). Il est possible de [[ide:eclipse:projet#creation_d_un_fichier_xml_sur_la_base_d_un_dtd_ou_schema|générer automatiquement le fichier xml]] ''hibernate.cfg.xml'' avec la clé ''DTD'' : ''%%-//Hibernate/Hibernate Configuration DTD 3.0//EN%%''. false ''hibernate.hbm2ddl.auto'' : * ''validate'' : validate the schema, makes no changes to the database. * ''update'' : update the schema. * ''create'' : creates the schema, destroying previous data. * ''create-drop'' : drop the schema at the end of the session. jdbc:mysql://localhost:3306/demojpa com.mysql.jdbc.Driver orm orm 10 org.hibernate.dialect.MySQLInnoDBDialect true org.hibernate.cache.NoCacheProvider false =====Les annotations===== ====Les classes==== Certaines classes ont besoin d'implémenter l'interface ''Serializable''.
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 : import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; @Entity @Inheritance(strategy = InheritanceType.JOINED) @Table(name="Videos") public class Video implements Serializable { @Id @GeneratedValue private Long idVideo; private String titre; private Integer annee; private static final long serialVersionUID = 1L; public Video() { super(); } public Long getIdVideo() { return this.idVideo; } public void setIdVideo(Long idVideo) { this.idVideo = idVideo; } public String getTitre() { return this.titre; } public void setTitre(String titre) { this.titre = titre; } public Integer getAnnee() { return this.annee; } public void setAnnee(Integer annee) { this.annee = annee; } } Deux classes enfants : import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.PrimaryKeyJoinColumn; import modeles.webscope.Artiste; import modeles.webscope.Video; @Entity @PrimaryKeyJoinColumn(name = "id_video") public class FilmV extends Video implements Serializable { private static final long serialVersionUID = 1L; @ManyToOne @JoinColumn(name = "id_realisateur") private Artiste realisateur; @ManyToOne private Genre genre; @ManyToOne private Pays pays; public FilmV() { super(); } public Artiste getRealisateur() { return this.realisateur; } public void setRealisateur(Artiste realisateur) { this.realisateur = realisateur; } public Genre getGenre() { return genre; } public void setGenre(Genre genre) { this.genre = genre; } public Pays getPays() { return pays; } public void setPays(Pays pays) { this.pays = pays; } } import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.PrimaryKeyJoinColumn; import modeles.webscope.Video; @Entity @PrimaryKeyJoinColumn(name = "id_video") public class Reportage extends Video implements Serializable { @Column(length = 40) private String lieu; private static final long serialVersionUID = 1L; public Reportage() { super(); } public String getLieu() { return this.lieu; } public void setLieu(String lieu) { this.lieu = lieu; } } Trois possibilités : 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'' // PK pour Primary Key import java.io.Serializable; import javax.persistence.Embeddable; // Pour que la classe puisse être intégré dans Personne // sans passer par une autre table @Embeddable // La classe doit implémenter Serializable public class PersonnePK implements Serializable { // Les champs privés qui serviront d'Id multiple. private String nom; private String prenom; // Les getter et les setter 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; } // hashCode et equals : généré automatiquement depuis Eclipse. @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((nom == null) ? 0 : nom.hashCode()); result = prime * result + ((prenom == null) ? 0 : prenom.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; PersonnePK other = (PersonnePK) obj; if (nom == null) { if (other.nom != null) return false; } else if (!nom.equals(other.nom)) return false; if (prenom == null) { if (other.prenom != null) return false; } else if (!prenom.equals(other.prenom)) return false; return true; } } import javax.persistence.EmbeddedId; import javax.persistence.Entity; @Entity public class Personne { @EmbeddedId private PersonnePK pk; private Long numSecu; private int compteEnBanque; public PersonnePK getPk() { return pk; } public void setPk(PersonnePK pk) { this.pk = pk; } 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; } } ===@IdClass=== Si plusieurs attributs composent la clé primaire, il est nécessaire de passer par une classe qui ne contient que les attributs id. ''@IdClass'' permet de définir laquelle est-ce. // Ici, on réutilise la classe PersonnePK sauf qu'il n'y a pas besoin de ''@Embeddable'' import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; @Entity @IdClass(PersonnePK.class) public class Personne2 { @Id private String nom; @Id private String prenom; private Long numSecu; private int compteEnBanque; 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; } } [[https://docs.oracle.com/cd/E16439_01/doc.1013/e13981/cmp30cfg001.htm|Configuring a JPA Entity Primary Key]] {{ :lang:java:e13981.pdf |Archive du 07/2019 le 26/04/2020}} ===@Temporal=== Nécessaire pour les formats ''java.util.Date'' et ''java.util.Calendar''. Il faut préciser la façon dont sera stocké l'information (''TemporalType.DATE'', ''TemporalType.TIME'', ''TemporalType.TIMESTAMP''). ===@Version=== Si plusieurs applications concurrentes accèdent à la même base de données, il peut être nécessaire de s'assurer que les données de l'application sont bien celles de la base de données. Il suffit d'ajouter un champ avec l'annotation ''@Version'' @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''. ?? ''mappedBy='' vaut le nom de la colonne ou le nom de l'attribut ?? ===@OneToMany=== Une instance de la classe fait référence à plusieurs instances de l'autre classe. Par exemple, un client peut avoir plusieurs comptes bancaires mais un compte bancaire ne peut appartenir qu'à un seul client. @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==== package modeles; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Pays implements Serializable { private static final long serialVersionUID = -2226639715151018263L; public Pays() { super(); } @Id private String code; public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Column private String nom; public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } @Column private String langue; public String getLangue() { return langue; } public void setLangue(String langue) { this.langue = langue; } } package modeles.webscope; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @Entity public class Film implements Serializable { @Id private Integer id; public void setId(Integer i) { id = i; } @Column private String titre; public void setTitre(String t) { titre = t; } public String getTitre() { return titre; } @Column private Integer annee; public void setAnnee(Integer a) { annee = a; } public Integer getAnnee() { return annee; } @ManyToOne @JoinColumn(name = "code_pays") private Pays pays; public void setPays(Pays p) { pays = p; } public Pays getPays() { return pays; } } ====EntityManager==== ===Initialisation de la connexion === Hibernate 5 : 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'' : ''''. ===Insertion=== Définition de l'objet à ajouter : 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' Ci-dessous, c'est le bordel... Et des jointures plus ou moins implicites pour des relations ''@ManyToOne'' et ''@OneToOne''. 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}}