Qu'est-ce que JPA? Introduction à l'API Java Persistence

En tant que spécification, l'API Java Persistence s'intéresse à la persistance , ce qui signifie en gros tout mécanisme par lequel les objets Java survivent au processus d'application qui les a créés. Tous les objets Java n'ont pas besoin d'être persistants, mais la plupart des applications conservent les objets métier clés. La spécification JPA vous permet de définir quels objets doivent être persistants et comment ces objets doivent être conservés dans vos applications Java.

En soi, JPA n'est pas un outil ou un cadre; il définit plutôt un ensemble de concepts qui peuvent être mis en œuvre par n'importe quel outil ou cadre. Alors que le modèle de mappage objet-relationnel (ORM) de JPA était à l'origine basé sur Hibernate, il a évolué depuis. De même, alors que JPA était à l'origine destiné à être utilisé avec des bases de données relationnelles / SQL, certaines implémentations de JPA ont été étendues pour une utilisation avec des banques de données NoSQL. Un framework populaire qui prend en charge JPA avec NoSQL est EclipseLink, l'implémentation de référence pour JPA 2.2.

JPA 2.2 à Jakarta EE

L'API Java Persistence a été publiée pour la première fois en tant que sous-ensemble de la spécification EJB 3.0 (JSR 220) dans Java EE 5. Elle a depuis évolué en tant que spécification propre, à commencer par la sortie de JPA 2.0 dans Java EE 6 (JSR 317). Au moment de la rédaction de cet article, JPA 2.2 a été adopté pour continuer dans le cadre de Jakarta EE.

JPA et Hibernate

En raison de leur histoire étroitement liée, Hibernate et JPA sont souvent confondus. Cependant, comme la spécification Java Servlet, JPA a engendré de nombreux outils et frameworks compatibles; Hibernate n'est que l'un d'entre eux.

Développé par Gavin King et publié au début de 2002, Hibernate est une bibliothèque ORM pour Java. King a développé Hibernate comme alternative aux beans entité pour la persistance. Le cadre était si populaire et si nécessaire à l'époque que nombre de ses idées ont été adoptées et codifiées dans la première spécification JPA.

Aujourd'hui, Hibernate ORM est l'une des implémentations JPA les plus matures et reste une option populaire pour ORM en Java. Hibernate ORM 5.3.8 (la version actuelle à ce jour) implémente JPA 2.2. De plus, la famille d'outils d'Hibernate s'est élargie pour inclure des outils populaires tels que Hibernate Search, Hibernate Validator et Hibernate OGM, qui prend en charge la persistance du modèle de domaine pour NoSQL.

JPA et EJB

Comme indiqué précédemment, JPA a été introduit en tant que sous-ensemble d'EJB 3.0, mais a depuis évolué en tant que spécification propre. EJB est une spécification avec un focus différent de JPA, et est implémentée dans un conteneur EJB. Chaque conteneur EJB comprend une couche de persistance, qui est définie par la spécification JPA.

Qu'est-ce que Java ORM?

Bien qu'elles diffèrent dans l'exécution, chaque implémentation JPA fournit une sorte de couche ORM. Afin de comprendre les outils compatibles JPA et JPA, vous devez avoir une bonne compréhension de l'ORM.

Le mappage objet-relationnel est une tâche que les développeurs ont de bonnes raisons d'éviter de faire manuellement. Un framework comme Hibernate ORM ou EclipseLink codifie cette tâche dans une bibliothèque ou un framework, une couche ORM . Dans le cadre de l'architecture applicative, la couche ORM est chargée de gérer la conversion des objets logiciels pour interagir avec les tables et colonnes d'une base de données relationnelle. En Java, la couche ORM convertit les classes et objets Java afin qu'ils puissent être stockés et gérés dans une base de données relationnelle.

Par défaut, le nom de l'objet persistant devient le nom de la table et les champs deviennent des colonnes. Une fois le tableau mis en place, chaque ligne du tableau correspond à un objet de l'application. Le mappage d'objets est configurable, mais les valeurs par défaut ont tendance à bien fonctionner.

JPA avec NoSQL

Jusqu'à récemment, les bases de données non relationnelles étaient des curiosités rares. Le mouvement NoSQL a changé tout cela, et maintenant une variété de bases de données NoSQL sont disponibles pour les développeurs Java. Certaines implémentations JPA ont évolué pour embrasser NoSQL, notamment Hibernate OGM et EclipseLink.

La figure 1 illustre le rôle de JPA et de la couche ORM dans le développement d'applications.

JavaWorld /

Configuration de la couche ORM Java

Lorsque vous configurez un nouveau projet pour utiliser JPA, vous devez configurer la banque de données et le fournisseur JPA. Vous allez configurer un connecteur de banque de données pour vous connecter à la base de données choisie (SQL ou NoSQL). Vous allez également inclure et configurer le fournisseur JPA , qui est un framework tel que Hibernate ou EclipseLink. Bien que vous puissiez configurer JPA manuellement, de nombreux développeurs choisissent d'utiliser la prise en charge prête à l'emploi de Spring. Voir « Installation et configuration de JPA » ci-dessous pour une démonstration de l'installation et de la configuration de JPA manuelle et Spring.

Objets de données Java

Java Data Objects est un cadre de persistance standardisé qui diffère de JPA principalement par la prise en charge de la logique de persistance dans l'objet et par sa prise en charge de longue date pour l'utilisation de magasins de données non relationnels. JPA et JDO sont suffisamment similaires pour que les fournisseurs de JDO prennent également souvent en charge JPA. Consultez le projet Apache JDO pour en savoir plus sur JDO par rapport à d'autres normes de persistance telles que JPA et JDBC.

Persistance des données en Java

Du point de vue de la programmation, la couche ORM est une couche adaptateur : elle adapte le langage des graphes d'objets au langage de SQL et des tables relationnelles. La couche ORM permet aux développeurs orientés objet de créer un logiciel qui persiste les données sans jamais quitter le paradigme orienté objet.

Lorsque vous utilisez JPA, vous créez une carte à partir de la banque de données vers les objets de modèle de données de votre application. Au lieu de définir comment les objets sont enregistrés et récupérés, vous définissez le mappage entre les objets et votre base de données, puis appelez JPA pour les conserver. Si vous utilisez une base de données relationnelle, une grande partie de la connexion réelle entre votre code d'application et la base de données sera alors gérée par JDBC, l'API Java Database Connectivity.

En tant que spécification, JPA fournit des annotations de métadonnées , que vous utilisez pour définir le mappage entre les objets et la base de données. Chaque implémentation JPA fournit son propre moteur pour les annotations JPA. La spécification JPA fournit également le PersistanceManagerou EntityManager, qui sont les principaux points de contact avec le système JPA (dans lequel votre code de logique métier indique au système quoi faire avec les objets mappés).

Pour rendre tout cela plus concret, considérez le Listing 1, qui est une classe de données simple pour modéliser un musicien.

Listing 1. Une classe de données simple en Java

 public class Musician { private Long id; private String name; private Instrument mainInstrument; private ArrayList performances = new ArrayList(); public Musician( Long id, String name){ /* constructor setters... */ } public void setName(String name){ this.name = name; } public String getName(){ return this.name; } public void setMainInstrument(Instrument instr){ this.instrument = instr; } public Instrument getMainInstrument(){ return this.instrument; } // ...Other getters and setters... } 

La Musicianclasse du listing 1 est utilisée pour contenir des données. Il peut contenir des données primitives telles que le champ de nom . Il peut également contenir des relations avec d'autres classes telles que mainInstrumentet performances.

Musician's reason for being is to contain data. This type of class is sometimes known as a DTO, or data transfer object. DTOs are a common feature of software development. While they hold many kinds of data, they do not contain any business logic. Persisting data objects is a ubiquitous challenge in software development.

Data persistence with JDBC

One way to save an instance of the Musician class to a relational database would be to use the JDBC library. JDBC is a layer of abstraction that lets an application issue SQL commands without thinking about the underlying database implementation.

Listing 2 shows how you could persist the Musician class using JDBC.

Listing 2. JDBC inserting a record

 Musician georgeHarrison = new Musician(0, "George Harrison"); String myDriver = "org.gjt.mm.mysql.Driver"; String myUrl = "jdbc:mysql://localhost/test"; Class.forName(myDriver); Connection conn = DriverManager.getConnection(myUrl, "root", ""); String query = " insert into users (id, name) values (?, ?)"; PreparedStatement preparedStmt = conn.prepareStatement(query); preparedStmt.setInt (1, 0); preparedStmt.setString (2, "George Harrison"); preparedStmt.setString (2, "Rubble"); preparedStmt.execute(); conn.close(); // Error handling removed for brevity 

The code in Listing 2 is fairly self-documenting. The georgeHarrison object could come from anywhere (front-end submit, external service, etc.), and has its ID and name fields set. The fields on the object are then used to supply the values of an SQL insert statement. (The PreparedStatement class is part of JDBC, offering a way to safely apply values to an SQL query.)

While JDBC allows the control that comes with manual configuration, it is cumbersome compared to JPA. In order to modify the database, you first need to create an SQL query that maps from your Java object to the tables in a relational database. You then have to modify the SQL whenever an object signature change. With JDBC, maintaining the SQL becomes a task in itself.

Data persistence with JPA

Now consider Listing 3, where we persist the Musician class using JPA.

Listing 3. Persisting George Harrison with JPA

 Musician georgeHarrison = new Musician(0, "George Harrison"); musicianManager.save(georgeHarrison); 

Listing 3 replaces the manual SQL from Listing 2 with a single line, session.save(), which instructs JPA to persist the object. From then on, the SQL conversion is handled by the framework, so you never have to leave the object-oriented paradigm.

Metadata annotations in JPA

The magic in Listing 3 is the result of a configuration, which is created using JPA's annotations. Developers use annotations to inform JPA which objects should be persisted, and how they should be persisted.

Listing 4 shows the Musician class with a single JPA annotation.

Listing 4. JPA's @Entity annotation

 @Entity public class Musician { // ..class body } 

Persistent objects are sometimes called entities. Attaching @Entity to a class like Musician informs JPA that this class and its objects should be persisted.

XML vs. annotation-based configuration

JPA also supports using external XML files, instead of annotations, to define class metadata. But why would you do that to yourself?

Configuring JPA

Like most modern frameworks, JPA embraces coding by convention (also known as convention over configuration), in which the framework provides a default configuration based on industry best practices. As one example, a class named Musician would be mapped by default to a database table called Musician.

The conventional configuration is a timesaver, and in many cases it works well enough. It is also possible to customize your JPA configuration. As an example, you could use JPA's @Table annotation to specify the table where the Musician class should be stored.

Listing 5. JPA's @Table annotation

 @Entity @Table(name="musician") public class Musician { // ..class body } 

Listing 5 tells JPA to persist the entity (Musician class) to the musician table.

Primary key

In JPA, the primary key is the field used to uniquely identify each object in the database. The primary key is useful for referencing and relating objects to other entities. Whenever you store an object in a table, you will also specify the field to use as its primary key.

In Listing 6, we tell JPA what field to use as Musician's primary key.

Listing 6. Specifying the primary key

 @Entity public class Musician { @Id private Long id; 

In this case, we've used JPA's @Id annotation to specify the id field as Musician's primary key. By default, this configuration assumes the primary key will be set by the database--for instance, when the field is set to auto-increment on the table.

JPA supports other strategies for generating an object's primary key. It also has annotations for changing individual field names. In general, JPA is flexible enough to adapt to any persistence mapping you might need.

CRUD operations

Once you've mapped a class to a database table and established its primary key, you have everything you need to create, retrieve, delete, and update that class in the database. Calling session.save() will create or update the specified class, depending on whether the primary-key field is null or applies to en existing entity. Calling entityManager.remove() will delete the specified class.

Entity relationships in JPA

Simply persisting an object with a primitive field is only half the equation. JPA also has the capability to manage entities in relation to one another. Four kinds of entity relationships are possible in both tables and objects:

    1. One-to-many
    2. Many-to-one
    3. Many-to-many
    4. One-to-one

Each type of relationship describes how an entity relates to other entities. For example, the Musician entity could have a one-to-many relationship with Performance, an entity represented by a collection such as List or Set.

If the Musician included a Band field, the relationship between these entities could be many-to-one, implying collection of Musicians on the single Band class. (Assuming each musician only performs in a single band.)

If Musician included a BandMates field, that could represent a many-to-many relationship with other Musician entities.

Enfin, Musicianpourrait avoir un tête-à-une relation avec une Quoteentité, utilisée pour représenter une citation célèbre: Quote famousQuote = new Quote().

Définition des types de relations

JPA a des annotations pour chacun de ses types de mappage de relations. Le listing 7 montre comment vous pourriez annoter la relation un-à-plusieurs entre Musicianet Performances.

Listing 7. Annotation d'une relation un-à-plusieurs