Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente |
lang:java:jpa [2017/01/28 18:33] – Divers ajouts root | lang:java:jpa [2020/05/11 00:31] (Version actuelle) – Suppression de la taille par défaut pour les images root |
---|
| |
=====Cycle de vie===== | =====Cycle de vie===== |
{{:lang:java:jpa:intro_02.png?954|Cycle de vie EJB}} | {{ :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/|Source]], {{ lang:java:jpa:java_ee_ejb_3_.htm.maff |Archive}} | [[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===== | =====persistence.xml===== |
<note>''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.</note> | <WRAP center round info 60%> |
| ''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. |
| </WRAP> |
''exclude-unlisted-classes'' : exclut toutes les classes non explicitement ''@Entity''. | ''exclude-unlisted-classes'' : exclut toutes les classes non explicitement ''@Entity''. |
| |
</file> | </file> |
''eclipselink.ddl-generation'' : | ''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-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. | * ''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. | * ''drop-and-create-tables'' : supprimer et recrée les tables à chaque lancement de l'application. |
Certaines classes ont besoin d'implémenter l'interface ''Serializable''. | Certaines classes ont besoin d'implémenter l'interface ''Serializable''. |
| |
<blockquote>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.<cite>JSR 338: Java TM Persistence API, Version 2.1[[https://jcp.org/en/jsr/detail?id=338|Source]], {{ :lang:java:jpa:javapersistence2.1.pdf |Archive}}</cite></blockquote> | <blockquote>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.<cite>[[https://jcp.org/en/jsr/detail?id=338|JSR 338: Java TM Persistence API, Version 2.1]], {{ :lang:java:jpa:javapersistence2.1.pdf |Archive}}</cite></blockquote> |
| |
La classe ne doit pas être finale et doit avoir un constructeur sans argument explicitement écrit avec une visibilité publique ou protégée. | La classe ne doit pas être finale et doit avoir un constructeur sans argument explicitement écrit avec une visibilité publique ou protégée. |
</code> | </code> |
Défini le nom de la table si elle est (ou doit être) différente du nom de la classe. | 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 : |
| <code java> |
| @Table(name="EMPLOYE", |
| uniqueConstraints={ |
| @UniqueConstraint(columnNames={"nom", "prenom"}) |
| }) |
| </code> |
| |
| ===@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=== | ===@Inheritance=== |
@Entity | @Entity |
@Inheritance(strategy = InheritanceType.JOINED) | @Inheritance(strategy = InheritanceType.JOINED) |
| @Table(name="Videos") |
public class Video implements Serializable { | public class Video implements Serializable { |
@Id | @Id |
| |
===@Id=== | ===@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://forums.manning.com/posts/list/31713.page|Source]], {{ lang:java:jpa:default_access_type.htm.maff |Archive}}. | 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''. | Il est conseillé de mettre le setter de ''@Id'' en ''private''. |
===@GeneratedValue=== | ===@GeneratedValue=== |
En complément à ''@Id'', il est possible d'indiquer que la génération de l'Id doit être automatique. | 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. | * ''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.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 [[http://www.thoughts-on-java.org/jpa-generate-primary-keys/|Source]], {{ :lang:java:jpa:how_to_generate_primary_keys_with_jpa_and_hibernate.htm.maff |Archive}} | * ''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. |
<code java> | <code java> |
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_generator") | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_generator") |
| |
===@Column=== | ===@Column=== |
Permet de spécifier les caractéristiques de la colonne de la base de données. | 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 : | 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 : |
* ''name = %%"XXXX"%%'' : le nom de la colonne, | ^Option^Description^Valeur par défaut^ |
* ''unique = true'' : si la colonne est composée de clés uniques, | |''name = %%"XXXX"%%''|Le nom de la colonne|Nom de la propriété ou de l'attribut.| |
* ''nullable = true'' : si les valeurs null sont acceptées, | |''unique = true''|Si la colonne est composée de clés uniques.|''false''| |
* ''length=32'' : nombre de caractères d'une colonne de type ''String'' uniquement. | |''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… | * etc… |
| |
</file> | </file> |
| |
[[https://docs.oracle.com/cd/E16439_01/doc.1013/e13981/cmp30cfg001.htm|Source]], {{ :lang:java:jpa:configuring_a_jpa_entity_primary_key.htm.maff |Archive}} | [[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=== | ===@Temporal=== |
| |
====Association/mapping==== | ====Association/mapping==== |
[[http://blog.paumard.org/cours/jpa/chap03-entite-relation.html|Source]], {{ :lang:java:jpa:4._mise_en_relation_d_entites.html.maff |Archive}} | [[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=== | ===@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. | 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. |
| |
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''. | 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''. |
| |
| <WRAP center round alert 60%> |
| ?? ''mappedBy='' vaut le nom de la colonne ou le nom de l'attribut ?? |
| </WRAP> |
| |
===@OneToMany=== | ===@OneToMany=== |
| |
@Entity | @Entity |
public class Film { | public class Film implements Serializable { |
@Id | @Id |
private Integer id; | private Integer id; |
</file> | </file> |
| |
====Code==== | ====EntityManager==== |
===Initialisation de la connexion === | ===Initialisation de la connexion === |
Hibernate 5 : | Hibernate 5 : |
| |
===Insertion=== | ===Insertion=== |
Hibernate : | Définition de l'objet à ajouter : |
<code java> | <code java> |
Pays monPays = new Pays(); | Pays monPays = new Pays(); |
monPays.setNom("Islande"); | monPays.setNom("Islande"); |
monPays.setLangue("Islandais"); | monPays.setLangue("Islandais"); |
| </code> |
| Hibernate : |
| <code java> |
session.beginTransaction(); | session.beginTransaction(); |
session.save(monPays); | session.save(monPays); |
session.getTransaction().commit(); | session.getTransaction().commit(); |
</code> | </code> |
| |
EclipseLink : | EclipseLink : |
<code java> | <code java> |
</code> | </code> |
* ''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. | * ''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. | * ''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=== | ===Lecture=== |
</code> | </code> |
| |
* HQL | <code java> |
| 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()) |
| { |
| ... |
| } |
| </code> |
| |
| Trouve en fonction de la clé primaire. |
| <code java> |
| Employe employe = em.find(Employe.class, 5233); |
| </code> |
| * HQL / JPQL |
| |
| Le ''HQL'' est la version spécifique du ''JPQL'' pour ''Hibernate''. |
Version simple | Version simple |
<code java> | <code java> |
List<Pays> resultat = query.getResultList(); | List<Pays> resultat = query.getResultList(); |
</code> | </code> |
| |
| 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. | Passer par des ''query'' avec des ''select'' permet d'éviter que le cache de premier niveau de ''Hibernate'' ne soit remplit. |
</code> | </code> |
| |
Et des jointures plus ou moins implicites pour des relations @ManyToOne et @OneToOne. | Exemple plus complet : |
| <code sql> |
| select employe |
| from Employe employe |
| where prenom = 'Pierre' |
| </code> |
| 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'') : |
| <code sql> |
| select employe.deptAffectation |
| from Employe employe |
| where employe.prenom = 'Pierre' |
| </code> |
| est identique à |
| <code sql> |
| select departement |
| from Employe employe join employe.deptAffectation departement |
| where employe.prenom = 'Pierre' |
| </code> |
| |
| On peut aussi forcer le préchargement (''fetch'') des données des tables jointes. |
| <code sql> |
| select employe |
| from Employe employe left join fetch employe.deptAffectation |
| where prenom = 'Pierre' |
| </code> |
| |
| |
| <WRAP center round important 60%> |
| Ci-dessous, c'est le bordel... |
| </WRAP> |
| |
| Et des jointures plus ou moins implicites pour des relations ''@ManyToOne'' et ''@OneToOne''. |
<code sql> | <code sql> |
select titre from Film f where f.realisateur.nom = 'Eastwood' | select titre from Film f where f.realisateur.nom = 'Eastwood' |
</code> | </code> |
| |
Il n'est pas possible de faire une recherches directement dans une liaison @ManyToMany ou @ManyToOne. Dans ces cas, il faut passer par une jointure. | 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. |
<code sql> | <code sql> |
select distinct film.titre from Film as film | select distinct film.titre from Film as film |
select film from Film as film join film.realisateur | select film from Film as film join film.realisateur |
where film.titre= :titre | 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' |
</code> | </code> |
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''. | 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''. |
<code sql> | <code sql> |
select film from Film as film join fetch film.realisateur | select film from Film as film left join fetch film.realisateur |
where film.titre= :titre | 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' |
</code> | </code> |
| |
| Mais ce n'est pas encore parfait car si on fait une recherche avec un critère dans une liste : |
| <code sql> |
| 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' |
| </code> |
| 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 : |
| <code sql> |
| 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' |
| ) |
| </code> |
| |
| 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 : | Réduire le nombre de résultats : |
<code java> | <code java> |
@Entity | @Entity |
@NamedNativeQueries(value = { | @NamedQueries(value = { |
@NamedNativeQuery | @NamedQuery |
( | ( |
resultClass=Commande.class, | resultClass=Commande.class, |
name = "TousLesPays", | name = "Pays.TousLesPays", |
query = "select p from Pays p" | query = "select p from Pays p" |
) | ) |
}) | }) |
| |
public class Pays implements Serializable { | public class Pays implements Serializable { |
… | … |
List<Pays> list = em.createNamedQuery("TousLesPays", Pays.class).getResultList(); | List<Pays> list = em.createNamedQuery("TousLesPays", Pays.class).getResultList(); |
</code> | </code> |
Lors qu'une classe ne possèque qu'une seule requête nommée, ''@NamedNativeQuery'' est suffisant. Sinon, il faut les ranger dans le paramètre ''value'' de ''@NamedNativeQueries'' sous forme d'un tableau. | 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}} |