JavaRush /Blog Java /Random-FR /JPA : Présentation de la technologie
Viacheslav
Niveau 3

JPA : Présentation de la technologie

Publié dans le groupe Random-FR
Le monde du développement moderne regorge de spécifications diverses conçues pour faciliter la vie. Connaissant les outils, vous pouvez choisir le bon. Sans le savoir, vous pouvez vous rendre la vie plus difficile. Cette revue lèvera le voile du secret sur le concept de JPA - Java Persistence API. J'espère qu'après avoir lu, vous aurez envie de plonger encore plus profondément dans ce monde mystérieux.
JPA : Introduction à la technologie - 1

Introduction

Comme nous le savons, l’une des tâches principales des programmes est le stockage et le traitement des données. Au bon vieux temps, les gens stockaient simplement les données dans des fichiers. Mais dès qu'un accès simultané en lecture et en édition est nécessaire, lorsqu'il y a une charge (c'est-à-dire que plusieurs requêtes arrivent en même temps), stocker les données simplement dans des fichiers devient un problème. Pour plus d'informations sur les problèmes que les bases de données résolvent et comment, je vous conseille de lire l'article « Comment les bases de données sont structurées ». Cela signifie que nous décidons de stocker nos données dans une base de données. Depuis longtemps, Java est capable de travailler avec des bases de données à l'aide de l'API JDBC (The Java Database Connectivity). Vous pouvez en savoir plus sur JDBC ici : « JDBC ou là où tout commence ». Mais le temps a passé et les développeurs ont été à chaque fois confrontés à la nécessité d'écrire le même type de code de « maintenance » inutile (appelé code Boilerplate) pour des opérations triviales de sauvegarde d'objets Java dans la base de données et vice versa, créant des objets Java en utilisant les données de la base de données. base de données. Et puis, pour résoudre ces problèmes, un concept tel que ORM est né. ORM - Mappage objet-relationnel ou traduit en mappage objet-relationnel russe. Il s'agit d'une technologie de programmation qui relie les bases de données aux concepts des langages de programmation orientés objet. Pour simplifier, ORM est la connexion entre les objets Java et les enregistrements dans une base de données : JPA : Introduction à la technologie - 2ORM est essentiellement le concept selon lequel un objet Java peut être représenté sous forme de données dans une base de données (et vice versa). Il a été incarné sous la forme de la spécification JPA - Java Persistence API. La spécification est déjà une description de l'API Java qui exprime ce concept. La spécification nous indique de quels outils nous devons disposer (c'est-à-dire quelles interfaces nous pouvons utiliser) pour travailler selon le concept ORM. Et comment utiliser ces fonds. Le cahier des charges ne décrit pas la mise en œuvre des outils. Cela permet d'utiliser différentes implémentations pour une même spécification. Vous pouvez le simplifier et dire qu'une spécification est une description de l'API. Le texte de la spécification JPA est disponible sur le site d'Oracle : « JSR 338 : JavaTM Persistence API ». Par conséquent, pour utiliser JPA, nous avons besoin d’une implémentation avec laquelle nous utiliserons la technologie. Les implémentations JPA sont également appelées fournisseurs JPA. L'une des implémentations JPA les plus remarquables est Hibernate . Par conséquent, je propose d’y réfléchir.
JPA : Introduction à la technologie - 3

Créer un projet

Puisque JPA concerne Java, nous aurons besoin d’un projet Java. Nous pourrions créer nous-mêmes manuellement la structure des répertoires et ajouter nous-mêmes les bibliothèques nécessaires. Mais il est beaucoup plus pratique et correct d'utiliser des systèmes pour automatiser l'assemblage de projets (c'est-à-dire, en substance, il s'agit simplement d'un programme qui gérera l'assemblage de projets pour nous. Créez des répertoires, ajoutez les bibliothèques nécessaires au chemin de classe, etc. .). L'un de ces systèmes est Gradle. Vous pouvez en savoir plus sur Gradle ici : " Une brève introduction à Gradle ". Comme nous le savons, la fonctionnalité Gradle (c'est-à-dire les choses qu'elle peut faire) est implémentée à l'aide de divers plugins Gradle. Utilisons Gradle et le plugin " Gradle Build Init Plugin ". Exécutons la commande :

gradle init --type java-application
Gradle fera la structure de répertoires nécessaire pour nous et créera une description déclarative de base du projet dans le script de construction build.gradle. Nous avons donc une candidature. Nous devons réfléchir à ce que nous voulons décrire ou modéliser avec notre application. Utilisons un outil de modélisation, par exemple : app.quickdatabasediagrams.com JPA : Introduction à la technologie - 4 Ici, il convient de dire que ce que nous avons décrit est notre « modèle de domaine ». Un domaine est un « domaine ». En général, domaine signifie « possession » en latin. Au Moyen Âge, c'était le nom donné aux domaines appartenant aux rois ou aux seigneurs féodaux. Et en français, c'est devenu le mot « domaine », qui se traduit simplement par « zone ». Ainsi nous avons décrit notre « modèle de domaine » = « modèle de sujet ». Chaque élément de ce modèle est une sorte d’« essence », quelque chose de la vie réelle. Dans notre cas, ce sont des entités : Catégorie ( Category), Sujet ( Topic). Créons un package séparé pour les entités, par exemple avec le nom model. Et ajoutons-y des classes Java qui décrivent les entités. Dans le code Java, ces entités sont des POJO classiques , qui peuvent ressembler à ceci :
public class Category {
    private Long id;
    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
Copions le contenu de la classe et créons une classe par analogie Topic. Il ne différera que par ce qu'il sait de la catégorie à laquelle il appartient. Par conséquent, ajoutons Topicun champ de catégorie et des méthodes pour travailler avec lui à la classe :
private Category category;

public Category getCategory() {
	return category;
}

public void setCategory(Category category) {
	this.category = category;
}
Nous avons maintenant une application Java qui possède son propre modèle de domaine. Il est maintenant temps de commencer à vous connecter au projet JPA.
JPA : Introduction à la technologie - 5

Ajout de JPA

Ainsi, comme nous nous en souvenons, JPA signifie que nous allons enregistrer quelque chose dans la base de données. Nous avons donc besoin d'une base de données. Pour utiliser une connexion à la base de données dans notre projet, nous devons ajouter une bibliothèque de dépendances pour nous connecter à la base de données. Si nous nous en souvenons, nous avons utilisé Gradle, qui a créé un script de construction pour nous build.gradle. Nous y décrirons les dépendances dont notre projet a besoin. Les dépendances sont ces bibliothèques sans lesquelles notre code ne peut pas fonctionner. Commençons par une description de la dépendance à la connexion à la base de données. Nous procédons de la même manière que si nous travaillions uniquement avec JDBC :

dependencies {
	implementation 'com.h2database:h2:1.4.199'
Nous avons maintenant une base de données. Nous pouvons maintenant ajouter une couche à notre application chargée de mapper nos objets Java dans des concepts de base de données (de Java à SQL). On s’en souvient, nous allons utiliser pour cela une implémentation de la spécification JPA appelée Hibernate :

dependencies {
	implementation 'com.h2database:h2:1.4.199'
	implementation 'org.hibernate:hibernate-core:5.4.2.Final'
Nous devons maintenant configurer JPA. Si nous lisons la spécification et la section « 8.1 Unité de persistance », nous saurons qu'une unité de persistance est une sorte de combinaison de configurations, de métadonnées et d'entités. Et pour que JPA fonctionne, vous devez décrire au moins une unité de persistance dans le fichier de configuration, appelée persistence.xml. Son emplacement est décrit dans le chapitre de spécifications « 8.2 Conditionnement des unités de persistance ». Selon cette section, si nous disposons d'un environnement Java SE, alors nous devons le mettre à la racine du répertoire META-INF.
JPA : Introduction à la technologie - 6
Copions le contenu de l'exemple donné dans la spécification JPA dans le 8.2.1 persistence.xml filechapitre " " :
<persistence>
	<persistence-unit name="JavaRush">
        <description>Persistence Unit For test</description>
        <class>hibernate.model.Category</class>
        <class>hibernate.model.Topic</class>
    </persistence-unit>
</persistence>
Mais ce n'est pas assez. Nous devons dire qui est notre fournisseur JPA, c'est-à-dire celui qui implémente la spécification JPA :
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
Ajoutons maintenant les paramètres ( properties). Certaines d'entre elles (commençant par javax.persistence) sont des configurations JPA standards et sont décrites dans la spécification JPA dans la section "8.2.1.9 propriétés". Certaines configurations sont spécifiques au fournisseur (dans notre cas, elles affectent Hibernate en tant que fournisseur Jpa. Notre bloc de paramètres ressemblera à ceci :
<properties>
    <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
    <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE" />
    <property name="javax.persistence.jdbc.user" value="sa" />
    <property name="javax.persistence.jdbc.password" value="" />
    <property name="hibernate.show_sql" value="true" />
    <property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
Nous avons maintenant une configuration compatible JPA persistence.xml, il existe un fournisseur JPA Hibernate et une base de données H2, et il existe également 2 classes qui sont notre modèle de domaine. Faisons enfin en sorte que tout cela fonctionne. Dans le catalogue /test/java, notre Gradle a gentiment généré un modèle pour les tests unitaires et l'a appelé AppTest. Utilisons-le. Comme indiqué dans le chapitre « 7.1 Contextes de persistance » de la spécification JPA, les entités du monde JPA vivent dans un espace appelé contexte de persistance. Mais nous ne travaillons pas directement avec Persistence Context. Pour cela, nous utilisons Entity Managerou "gestionnaire d'entité". C'est lui qui connaît le contexte et quelles entités y vivent. Nous interagissons avec Entity Manager'om. Il ne reste plus qu'à comprendre d'où on peut se procurer celui-là Entity Manager? D'après le chapitre "7.2.2 Obtention d'un gestionnaire d'entités géré par application" de la spécification JPA, nous devons utiliser EntityManagerFactory. Armons-nous donc de la spécification JPA et prenons un exemple du chapitre « 7.3.2 Obtention d'une Entity Manager Factory dans un environnement Java SE » et formatons-le sous la forme d'un simple test unitaire :
@Test
public void shouldStartHibernate() {
	EntityManagerFactory emf = Persistence.createEntityManagerFactory( "JavaRush" );
	EntityManager entityManager = emf.createEntityManager();
}
Ce test affichera déjà l'erreur "Version XSD JPA persistence.xml non reconnue". La raison est que persistence.xmlvous devez spécifier correctement le schéma à utiliser, comme indiqué dans la spécification JPA dans la section « 8.3 Schéma persistence.xml » :
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
             version="2.2">
De plus, l’ordre des éléments est important. providerIl faut donc le préciser avant de lister les classes. Après cela, le test s'exécutera avec succès. Nous avons terminé la connexion directe JPA. Avant de continuer, réfléchissons aux tests restants. Chacun de nos tests nécessitera EntityManager. Assurons-nous que chaque test a le sien EntityManageren début d'exécution. De plus, nous souhaitons que la base de données soit nouvelle à chaque fois. Du fait que nous utilisons inmemoryl'option, il suffit de fermer EntityManagerFactory. La création Factoryest une opération coûteuse. Mais pour les tests, c'est justifié. JUnit permet de spécifier les méthodes qui seront exécutées avant (Avant) et après (Après) l'exécution de chaque test :
public class AppTest {
    private EntityManager em;

    @Before
    public void init() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory( "JavaRush" );
        em = emf.createEntityManager();
    }

    @After
    public void close() {
        em.getEntityManagerFactory().close();
        em.close();
    }
Désormais, avant d'exécuter un test, un nouveau sera créé EntityManagerFactory, ce qui entraînera la création d'une nouvelle base de données, car hibernate.hbm2ddl.autoa le sens create. Et de la nouvelle usine, nous en aurons une nouvelle EntityManager.
JPA : Introduction à la technologie - 7

Entités

Si nous nous en souvenons, nous avons précédemment créé des classes qui décrivent notre modèle de domaine. Nous avons déjà dit que ce sont nos « essences ». C'est l'entité que nous allons gérer en utilisant EntityManager. Écrivons un test simple pour sauvegarder l'essence d'une catégorie :
@Test
public void shouldPersistCategory() {
	Category cat = new Category();
	cat.setTitle("new category");
	// JUnit обеспечит тест свежим EntityManager'ом
	em.persist(cat);
}
Mais ce test ne fonctionnera pas tout de suite, car... nous recevrons diverses erreurs qui nous aideront à comprendre ce que sont les entités :
  • Unknown entity: hibernate.model.Category
    Pourquoi Hibernate ne comprend-il pas ce que Categoryc'est entity? Le fait est que les entités doivent être décrites selon la norme JPA.
    Les classes d'entités doivent être annotées avec l'annotation @Entity, comme indiqué dans le chapitre « 2.1 La classe d'entité » de la spécification JPA.

  • No identifier specified for entity: hibernate.model.Category
    Les entités doivent avoir un identifiant unique qui peut être utilisé pour distinguer un enregistrement d'un autre.
    Selon le chapitre "2.4 Clés primaires et identité d'entité" de la spécification JPA, "Chaque entité doit avoir une clé primaire", c'est-à-dire Chaque entité doit avoir une « clé primaire ». Une telle clé primaire doit être spécifiée par l'annotation@Id

  • ids for this class must be manually assigned before calling save()
    La pièce d'identité doit venir de quelque part. Il peut être spécifié manuellement ou obtenu automatiquement.
    Ainsi, comme indiqué dans les chapitres « 11.2.3.3 GeneratedValue » et « 11.1.20 GeneratedValue Annotation », nous pouvons spécifier l'annotation @GeneratedValue.

Ainsi, pour que la classe catégorie devienne une entité, nous devons apporter les modifications suivantes :
@Entity
public class Category {
    @Id
    @GeneratedValue
    private Long id;
De plus, l'annotation @Idindique lequel utiliser Access Type. Vous pouvez en savoir plus sur le type d'accès dans la spécification JPA, dans la section "2.3 Type d'accès". Pour le dire très brièvement, parce que... nous avons spécifié @Idci-dessus le champ ( field), alors le type d'accès sera par défaut field-based, non property-based. Par conséquent, le fournisseur JPA lira et stockera les valeurs directement à partir des champs. Si nous placions @Idau-dessus du getter, alors property-basedl'accès serait utilisé, c'est-à-dire via getter et setter. Lors de l'exécution du test, nous voyons également quelles requêtes sont envoyées à la base de données (grâce à l'option hibernate.show_sql). Mais lors de la sauvegarde, nous ne voyons aucun insert« ». Il s’avère que nous n’avons en fait rien économisé ? JPA permet de synchroniser le contexte de persistance et la base de données grâce à la méthode flush:
entityManager.flush();
Mais si nous l'exécutons maintenant, nous obtiendrons une erreur : aucune transaction n'est en cours . Et maintenant, il est temps de découvrir comment JPA utilise les transactions.
JPA : Introduction à la technologie - 8

Transactions JPA

On le rappelle, JPA repose sur la notion de contexte de persistance. C'est l'endroit où vivent les entités. Et nous gérons les entités via EntityManager. Lorsque nous exécutons la commande persist, nous plaçons l'entité dans le contexte. Plus précisément, nous EntityManagerleur disons que cela doit être fait. Mais ce contexte n’est qu’un espace de stockage. On l'appelle même parfois le « cache de premier niveau ». Mais il doit être connecté à la base de données. La commande flush, qui avait précédemment échoué avec une erreur, synchronise les données du contexte de persistance avec la base de données. Mais cela nécessite du transport et ce transport est une transaction. Les transactions dans JPA sont décrites dans la section « 7.5 Contrôle des transactions » de la spécification. Il existe une API spéciale pour utiliser les transactions dans JPA :
entityManager.getTransaction().begin();
entityManager.getTransaction().commit();
Nous devons ajouter la gestion des transactions à notre code, qui s'exécute avant et après les tests :
@Before
public void init() {
	EntityManagerFactory emf = Persistence.createEntityManagerFactory( "JavaRush" );
	em = emf.createEntityManager();
	em.getTransaction().begin();
}
@After
public void close() {
	if (em.getTransaction().isActive()) {
		em.getTransaction().commit();
        }
	em.getEntityManagerFactory().close();
	em.close();
}
Après l'ajout, nous verrons dans le journal d'insertion une expression en SQL qui n'était pas là auparavant :
JPA : Introduction à la technologie - 9
Les modifications accumulées dans EntityManagerla transaction ont été validées (confirmées et enregistrées) dans la base de données. Essayons maintenant de trouver notre essence. Créons un test pour rechercher une entité par son ID :
@Test
public void shouldFindCategory() {
	Category cat = new Category();
	cat.setTitle("test");
	em.persist(cat);
	Category result = em.find(Category.class, 1L);
	assertNotNull(result);
}
Dans ce cas, nous recevrons l'entité que nous avons précédemment enregistrée, mais nous ne verrons pas les requêtes SELECT dans le journal. Et tout est basé sur ce que nous disons : « Entity Manager, veuillez me trouver l'entité Catégorie avec ID=1. » Et le gestionnaire d’entités regarde d’abord dans son contexte (utilise une sorte de cache), et seulement s’il ne le trouve pas, il va chercher dans la base de données. Cela vaut la peine de changer l'ID par 2 (cela n'existe pas, nous n'avons enregistré qu'une seule instance), et nous verrons que SELECTla requête apparaît. Parce qu'aucune entité n'a été trouvée dans le contexte et EntityManagerque la base de données essaie de trouver une entité, il existe différentes commandes que nous pouvons utiliser pour contrôler l'état d'une entité dans le contexte. La transition d'une entité d'un état à un autre est appelée le cycle de vie de l'entité - lifecycle.
JPA : Introduction à la technologie - 10

Cycle de vie de l'entité

Le cycle de vie des entités est décrit dans la spécification JPA au chapitre « 3.2 Cycle de vie des instances d'entité ». Parce que les entités vivent dans un contexte et sont contrôlées par EntityManager, alors ils disent que les entités sont contrôlées, c'est-à-dire géré. Regardons les étapes de la vie d'une entité :
// 1. New or Transient (временный)
Category cat = new Category();
cat.setTitle("new category");
// 2. Managed or Persistent
entityManager.persist(cat);
// 3. Транзакция завершена, все сущности в контексте detached
entityManager.getTransaction().begin();
entityManager.getTransaction().commit();
// 4. Сущность изымаем из контекста, она становится detached
entityManager.detach(cat);
// 5. Сущность из detached можно снова сделать managed
Category managed = entityManager.merge(cat);
// 6. И можно сделать Removed. Интересно, что cat всё равно detached
entityManager.remove(managed);
Et voici un schéma pour le consolider :
JPA : Introduction à la technologie - 11
JPA : Introduction à la technologie - 12

Cartographie

En JPA, nous pouvons décrire les relations des entités entre elles. Rappelons que nous avons déjà examiné les relations des entités entre elles lorsque nous avons traité de notre modèle de domaine. Ensuite nous avons utilisé la ressource quickdatabasediagrams.com :
JPA : Introduction à la technologie - 13
L'établissement de connexions entre entités est appelé mappage ou association (Association Mappings). Les types d'associations pouvant être établies à l'aide de JPA sont présentés ci-dessous :
JPA : Introduction à la technologie - 14
Regardons une entité Topicqui décrit un sujet. Que pouvons-nous dire de l'attitude Topicenvers Category? Beaucoup Topicappartiendront à une seule catégorie. Nous avons donc besoin d'une association ManyToOne. Exprimons cette relation en JPA :
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
Pour mémoriser quelles annotations mettre, vous pouvez rappeler que la dernière partie est responsable du champ au dessus duquel l'annotation est indiquée. ToOne- cas précis. ToMany- les recueils. Désormais, notre connexion est à sens unique. Faisons-en une communication bidirectionnelle. Ajoutons aux Categoryconnaissances sur toutes les personnes Topicincluses dans cette catégorie. Cela doit se terminer par ToMany, car nous avons une liste Topic. C'est-à-dire l'attitude « envers de nombreux » sujets. La question demeure - OneToManyou ManyToMany:
JPA : Introduction à la technologie - 15
Une bonne réponse sur le même sujet peut être lue ici : " Expliquez la relation ORM oneToMany, manyToMany comme si j'avais cinq ans ". Si une catégorie a un lien avec ToManydes sujets, alors chacun de ces sujets ne peut avoir qu'une seule catégorie, alors ce sera One, sinon Many. CategoryLa liste de tous les sujets ressemblera donc à ceci :
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "topic_id")
private Set<Topic> topics = new HashSet<>();
Et n'oublions pas essentiellement d' Categoryécrire un getter pour obtenir une liste de tous les sujets :
public Set<Topic> getTopics() {
	return this.topics;
}
Les relations bidirectionnelles sont une chose très difficile à suivre automatiquement. Par conséquent, JPA transfère cette responsabilité au développeur. Cela signifie pour nous que lorsque nous établissons une Topicrelation d'entité avec Category, nous devons nous-mêmes assurer la cohérence des données. Cela se fait simplement :
public void setCategory(Category category) {
	category.getTopics().add(this);
	this.category = category;
}
Écrivons un test simple pour vérifier :
@Test
public void shouldPersistCategoryAndTopics() {
	Category cat = new Category();
	cat.setTitle("test");
	Topic topic = new Topic();
	topic.setTitle("topic");
	topic.setCategory(cat);
 	em.persist(cat);
}
La cartographie est un tout autre sujet. Le but de cette revue est de comprendre les moyens par lesquels cet objectif est atteint. Vous pouvez en savoir plus sur la cartographie ici :
JPA : Introduction à la technologie - 16

JPQL

JPA introduit un outil intéressant : les requêtes dans le langage de requête de persistance Java. Ce langage est similaire à SQL, mais utilise le modèle objet Java plutôt que les tables SQL. Regardons un exemple :
@Test
public void shouldPerformQuery() {
	Category cat = new Category();
	cat.setTitle("query");
	em.persist(cat);
	Query query = em.createQuery("SELECT c from Category c WHERE c.title = 'query'");
 	assertNotNull(query.getSingleResult());
}
Comme nous pouvons le voir, dans la requête nous avons utilisé une référence à une entité Categoryet non à une table. Et aussi sur le terrain de cette entité title. JPQL offre de nombreuses fonctionnalités utiles et mérite son propre article. Plus de détails peuvent être trouvés dans la revue :
JPA : Introduction à la technologie - 17

API de critères

Et enfin, je voudrais aborder l’API Criteria. JPA introduit un outil de création de requêtes dynamiques. Exemple d'utilisation de l'API Criteria :
@Test
public void shouldFindWithCriteriaAPI() {
	Category cat = new Category();
	em.persist(cat);
	CriteriaBuilder cb = em.getCriteriaBuilder();
	CriteriaQuery<Category> query = cb.createQuery(Category.class);
	Root<Category> c = query.from(Category.class);
	query.select(c);
	List<Category> resultList = em.createQuery(query).getResultList();
	assertEquals(1, resultList.size());
}
Cet exemple équivaut à exécuter la requête " SELECT c FROM Category c". L'API Criteria est un outil puissant. Vous pouvez en savoir plus ici :

Conclusion

Comme nous pouvons le constater, JPA fournit un grand nombre de fonctionnalités et d’outils. Chacun d’eux nécessite de l’expérience et des connaissances. Même dans le cadre de l’examen du JPA, il n’a pas été possible de tout mentionner, encore moins de plonger dans le détail. Mais j'espère qu'après l'avoir lu, il est devenu plus clair ce que sont ORM et JPA, comment ils fonctionnent et ce qu'on peut en faire. Eh bien, pour le goûter je vous propose divers matériels : #Viacheslav
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION