Cos'è JPA? Introduzione all'API Java Persistence

Come specifica, l'API Java Persistence riguarda la persistenza , il che significa vagamente qualsiasi meccanismo mediante il quale gli oggetti Java sopravvivono al processo dell'applicazione che li ha creati. Non tutti gli oggetti Java devono essere persistenti, ma la maggior parte delle applicazioni mantiene gli oggetti di business chiave. La specifica JPA consente di definire quali oggetti devono essere mantenuti e in che modo tali oggetti devono essere mantenuti nelle applicazioni Java.

Di per sé, JPA non è uno strumento o una struttura; piuttosto, definisce un insieme di concetti che possono essere implementati da qualsiasi strumento o framework. Sebbene il modello ORM (object-relational mapping) di JPA fosse originariamente basato su Hibernate, da allora si è evoluto. Allo stesso modo, mentre JPA era originariamente concepito per l'uso con database relazionali / SQL, alcune implementazioni JPA sono state estese per l'uso con datastore NoSQL. Un framework popolare che supporta JPA con NoSQL è EclipseLink, l'implementazione di riferimento per JPA 2.2.

JPA 2.2 a Jakarta EE

Java Persistence API è stata rilasciata per la prima volta come sottoinsieme della specifica EJB 3.0 (JSR 220) in Java EE 5. Da allora si è evoluta come specifica propria, a partire dal rilascio di JPA 2.0 in Java EE 6 (JSR 317). Al momento della stesura di questo documento, JPA 2.2 è stato adottato per la continuazione come parte di Jakarta EE.

JPA e Hibernate

A causa della loro storia intrecciata, Hibernate e JPA sono spesso fusi. Tuttavia, come la specifica Java Servlet, JPA ha generato molti strumenti e framework compatibili; Hibernate è solo uno di questi.

Sviluppata da Gavin King e rilasciata all'inizio del 2002, Hibernate è una libreria ORM per Java. King ha sviluppato Hibernate come alternativa ai bean di entità per la persistenza. Il framework era così popolare e così necessario all'epoca che molte delle sue idee furono adottate e codificate nella prima specifica JPA.

Oggi, Hibernate ORM è una delle implementazioni JPA più mature e ancora un'opzione popolare per ORM in Java. Hibernate ORM 5.3.8 (la versione corrente al momento della stesura di questo documento) implementa JPA 2.2. Inoltre, la famiglia di strumenti di Hibernate si è espansa per includere strumenti popolari come Hibernate Search, Hibernate Validator e Hibernate OGM, che supporta la persistenza del modello di dominio per NoSQL.

JPA e EJB

Come notato in precedenza, JPA è stato introdotto come sottoinsieme di EJB 3.0, ma da allora si è evoluto come una propria specifica. EJB è una specifica con un focus diverso da JPA ed è implementata in un contenitore EJB. Ogni contenitore EJB include un livello di persistenza, definito dalla specifica JPA.

Cos'è Java ORM?

Sebbene differiscano nell'esecuzione, ogni implementazione JPA fornisce una sorta di livello ORM. Per comprendere gli strumenti compatibili con JPA e JPA, è necessario avere una buona conoscenza di ORM.

La mappatura relazionale degli oggetti è un'attività che gli sviluppatori hanno buone ragioni per evitare di eseguire manualmente. Un framework come Hibernate ORM o EclipseLink codifica quell'attività in una libreria o framework, un livello ORM . Come parte dell'architettura dell'applicazione, il livello ORM è responsabile della gestione della conversione degli oggetti software per interagire con le tabelle e le colonne in un database relazionale. In Java, il livello ORM converte le classi e gli oggetti Java in modo che possano essere archiviati e gestiti in un database relazionale.

Per impostazione predefinita, il nome dell'oggetto che viene reso persistente diventa il nome della tabella ei campi diventano colonne. Una volta impostata la tabella, ogni riga della tabella corrisponde a un oggetto nell'applicazione. La mappatura degli oggetti è configurabile, ma i valori predefiniti tendono a funzionare bene.

JPA con NoSQL

Fino a tempi abbastanza recenti, i database non relazionali erano curiosità non comuni. Il movimento NoSQL ha cambiato tutto questo e ora una varietà di database NoSQL è disponibile per gli sviluppatori Java. Alcune implementazioni JPA si sono evolute per abbracciare NoSQL, inclusi Hibernate OGM ed EclipseLink.

La Figura 1 illustra il ruolo di JPA e del livello ORM nello sviluppo dell'applicazione.

JavaWorld /

Configurazione del livello ORM Java

Quando imposti un nuovo progetto per utilizzare JPA, dovrai configurare il datastore e il provider JPA. Configurerai un connettore del datastore per connettersi al database scelto (SQL o NoSQL). Includerai e configurerai anche il provider JPA , che è un framework come Hibernate o EclipseLink. Sebbene sia possibile configurare manualmente JPA, molti sviluppatori scelgono di utilizzare il supporto predefinito di Spring. Vedere " Installazione e configurazione JPA " di seguito per una dimostrazione dell'installazione e configurazione JPA manuale e basata su Spring.

Oggetti dati Java

Java Data Objects è un framework di persistenza standardizzato che differisce da JPA principalmente per il supporto della logica di persistenza nell'oggetto e per il suo supporto di lunga data per lavorare con archivi dati non relazionali. JPA e JDO sono abbastanza simili che spesso anche i provider JDO supportano JPA. Vedere il progetto Apache JDO per ulteriori informazioni su JDO in relazione ad altri standard di persistenza come JPA e JDBC.

Persistenza dei dati in Java

Dal punto di vista della programmazione, il livello ORM è un livello adattatore : adatta il linguaggio degli oggetti grafici al linguaggio dell'SQL e delle tabelle relazionali. Il livello ORM consente agli sviluppatori orientati agli oggetti di creare software che resista i dati senza mai lasciare il paradigma orientato agli oggetti.

Quando si utilizza JPA, si crea una mappa dal datastore agli oggetti del modello dati dell'applicazione. Invece di definire il modo in cui gli oggetti vengono salvati e recuperati, si definisce la mappatura tra gli oggetti e il database, quindi si richiama JPA per renderli persistenti. Se si utilizza un database relazionale, gran parte della connessione effettiva tra il codice dell'applicazione e il database verrà quindi gestita da JDBC, l'API di connettività del database Java.

Come specifica, JPA fornisce annotazioni di metadati , che vengono utilizzate per definire la mappatura tra gli oggetti e il database. Ogni implementazione JPA fornisce il proprio motore per le annotazioni JPA. La specifica JPA fornisce anche gli PersistanceManagero EntityManager, che sono i punti chiave di contatto con il sistema JPA (in cui il codice della logica di business dice al sistema cosa fare con gli oggetti mappati).

Per rendere tutto questo più concreto, si consideri il Listato 1, che è una semplice classe di dati per modellare un musicista.

Listato 1. Una semplice classe di dati in 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 nel listato 1 viene utilizzata per contenere i dati. Può contenere dati primitivi come il campo del nome . Può anche mantenere relazioni con altre classi come mainInstrumente 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.

Infine, Musicianpotrebbe avere una relazione uno-a-uno con un Quoteentità, utilizzato per rappresentare una citazione famosa: Quote famousQuote = new Quote().

Definizione dei tipi di relazione

JPA ha annotazioni per ciascuno dei suoi tipi di mappatura delle relazioni. Il Listato 7 mostra come annotare la relazione uno-a-molti tra Musiciane Performances.

Listato 7. Annotazione di una relazione uno-a-molti