Introduzione a JPA

Marco Andreini

Architettura multi strato

Per creare applicazioni - anche complesse - si può utilizzare una architettura multi-strato (n-tier).

Ciascuno strato:

  • si occupa di un insieme di funzionalità logicamente separato;

  • comunica con il precedente e il successivo;

  • è sviluppato separatamente e indipendentemente dagli altri.

Architettura a tre livelli

Le applicazioni web sono spesso strutturate su three-tier architecture:

  1. livello di presentazione, con la quale si interagisce direttamente con l’utente

  2. livello applicativo (businness logic), si occupa della logica di funzionamento

  3. livello dati, si occupa dell’immagazzinamento dei dati su database

Object Relational Mapping

  • L’ORM si occupa di automatizzare il processo di trasformazione dei dati tra gli oggetti (istanze di classi) e i database.

  • Perché è utile?

    • semplifica enormemente il lavoro di interfacciamento al database;

    • evita di dover scrivere le procedure di trasformazione a mano;

    • ci vogliamo concentrare sulla logica di funzionamento e non sulla struttura relazionale del database.

ORM: altri benefici

  • Permette una eventuale sincronizzazione automatica degli oggetti su database

  • È piuttosto indipendente dal tipo di database

  • Astrazione sui metodi di interrogazione

  • Prestazioni: utilizzando meccanismi di cache semi-automatici

Java Persistence API

  • JPA è una specifica java (JSR) per accedere, memorizzare e gestire dati di oggetti Java specifici e database relazionali.

  • È confinato nel package javax.persistence e non fa parte della dotazione standard JavaSE.

  • È considerato l'ORM standard in ambiente Java.

  • È stato inizialmente definito nella specifica EJB3.0.

Hibernate come JPA

Hibernate è un ORM che implementa la specifica JPA.

  • Esistono diverse specifiche JPA (1.0, 2.0, 2.1…)

  • Queste specifiche sono implementate in Hibernate in varie versioni.

  • Noi utilizzeremo come riferimento la specifica JPA 2.0, che è implementata in Hibernate a partire dalla 4.0

Una entity

@Entity                             (1)
public class BlogEntry {
  @Id @GeneratedValue               (2)
  private Integer id;
  @Column(nullable=false)           (3)
  private String title;
  private String summary;           (4)
  private String body;              (4)
  @Temporal(TemporalType.TIMESTAMP) (5)
  private Date date;
}
1indica che l’entity si riferisce a una TABLE di nome blog_entry
2si riferisce alla colonna id, chiave primaria autoincrementale
3si riferisce alla colonna title, di tipo VARCHAR NOT NULL
4si riferiscono a colonne di tipo VARCHAR
5si riferisce alla colonna date di tipo 'TIMESTAMP'

JPA e POJO

Si utilizzano oggetti di tipo POJO (Plain Old Java Object) e la persistenza riguarda:

  • il salvataggio dei valori dei campi di questi oggetti

  • la loro consultazione

  • la loro gestione (aggiornamento, cancellazione,…​)

  • ogni oggetto tipicamente rappresenta una riga di una tabella

Annotazioni JPA

  • Le caratteristiche degli oggetti coinvolti - prevalentemente entity - sono definite da annotazioni specifiche.

  • Queste annotazioni indicano quale comportamento deve avere il JPA (ciò che implementa la specifica JPA, per esempio Hibernate).

    • In precedenza si utilizzavano file di mappatura entity-table inseriti in appositi file XML (.hbm.xml per Hibernate).

Mappatura automatica

In JPA è possibile utilizzare l’associazione automatica tra istanze in java e tabelle in funzione delle annotazioni presenti sulle entity.

  • l’associazione viene effettuata all’avvio del sistema JPA

  • tipicamente viene effettuato il classpath scan (ricerca nelle classi java)

  • è anche possibile indicare esplicitamente quali siano le classi da persistere

@Entity

@Entity                             (1)
public class BlogEntry {
  @Id @GeneratedValue               (2)
  private Integer id;
  @Column(nullable=false)           (3)
  private String title;
  private String summary;           (4)
  private String body;              (4)
  @Temporal(TemporalType.TIMESTAMP) (5)
  private Date date;
}
1indica che l’entity si riferisce a una TABLE di nome blog_entry
2si riferisce alla colonna id, chiave primaria autoincrementale
3si riferisce alla colonna title, di tipo VARCHAR NOT NULL
4si riferiscono a colonne di tipo VARCHAR
5si riferisce alla colonna date di tipo 'TIMESTAMP'

Persistence Unit

  • è definito dall’insieme di classi gestite da un’istanza dell’EntityManager.

  • definisce la corrispondaza di un insieme di classi di entity con un database relazionale

  • effettua la serializzazione e deserializzazione controllate verso/dal database relazionale

Persistence Unit(2)

  • è definito per mezzo del framework che implementa la specifica JPA

  • tipicamente si definiscono le Persistence Unit nel filel META-INF/persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
  version="1.0">
  <persistence-unit name="myunit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="connection.driver_class" value="org.h2.Driver" />
      <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
      <property name="hibernate.hbm2ddl.auto" value="update" />
      <property name="hibernate.show_sql" value="true" />
    </properties>
  </persistence-unit>

Persistence Context

  • è l’insieme delle istanze di entity in un certo insieme di dati

  • conosce lo stato delle istanze di entity rispetto al database (detached, managed,…​)

@Id

Occorre annotare con @Id il campo che identifica la chiave primaria

  • Solitamente è di tipo numerico o stringa

  • Ci deve essere un solo campo annotato con @Id per entity.

  • Visto che le entity sono confrontate tramite il metodo equals, occorre che si utilizzi in questi confronti il campo annotato con @Id.

Chiavi primarie

  • Se necessario è possibile creare chiavi primarie composte – anche se solitamente non è consigliabile.

  • Si può indicare una strategia per la generazione automatica dei valori delle chiavi primarie con:

  @Id // identifica la chiave primaria
  @GeneratedValue // indica che il valore è autogenerato dall'ORM/DB
  private Integer id;

@Column

L’annotazione @Column può essere utilizzata su tutti i campi che corrispondono ad una colonna per:

  • indicare il nome della colonna corrispondente (con name=…​), che altrimenti sarà il nome del campo (opportunamente traslato)

  • indicare se il campo può assumere i valori null (default) o meno (con nullable=false)

  • indicare se la colonna corrispondente deve avere una chiave univoca (con unique=true)

Altri attributi usati più di rado: columnDefinition, length, insertable, updatable, …​

Schema

  • In Hibernate - ma anche con altri framework JPA - è possibile

    • definire come far interagire il database in fase di creazione del Persistence Context.

    • si possono verificare/creare/distruggere le tabelle in automatico

    • è comunque un comportamento opzionale

    • ciò permette di scegliere in funzione della fase progettuale il comportamento più indicato

Schema(2)

Più nel dettaglio in Hibernate si può utilizzare uno di questi valori:

validate

valida lo schema (utile in fase di produzione o pre-produzione)

update

modifica se necessario lo schema (utile a volte in sviluppo)

create

crea le tabelle mancanti nello schema (utile per i test)

create-drop

distrugge le tabelle coinvolte e le ricrea (utile per i test)

Differenze tra attributi delle annotazioni

  • Gli attributi delle annotazioni del package javax.persistence hanno effetti differenti sull’ORM

  • alcune annotazioni/attributi come ad esempio il @Column(name="mycolumn") indicano

    • in fase di creazione della tabella che il nome della colonna sarà mycolumn

    • durante le SELECT/INSERT/UPDATE il campo sarà associato alla colonna mycolumn

Differenze tra attributi della annotazioni(2)

  • Altri attributi/annotazioni sono maggiormente di struttura

  • ad esempio @Column(nullable=false) indica

    • in fase di creazione della tabella che la colonna in questione dovrà essere NOT NULL

    • durante le SELECT/INSERT/UPDATE non ha praticamente effetti

@Table

Con @Table si possono annotare le entity con informazioni aggiuntive, che indicano:

  • il nome della tabella (altrimenti è quello dell’entity)

  • il nome del catalogo

  • il nome dello schema

  • eventuali vincoli di univocità (uniqueConstraints)

  • eventuali indici (indexes)

EntityManager

  • permette di interagire col Persistence Context

  • ha una API per gestire le istanze delle entity, interagendo col loro stato

  • permette di caricare, salvare, cercare…​ le istanze delle entity

Come ottenere l’EntityManager

  • In modo dipendente dal framework in uso l’entityManager è:

    • Injected dal framework in automatico (es. @PersistenceContext)

    • Creato a partire dall’EntityManagerFactory, creato e configurato una sola volta all’avvio dell’applicazione.

Operazioni di base sulle entity

  • creazione → EntityManager::persist

  • recupero → EntityManager::find

  • cancellazione → EntityManager::remove

EntityManager::persist

Le entità sono regolari oggetti java fino a quando non vengono resi persistenti dall’EntityManager

public void doThings() {
 // ...
 entityManager.persist(myBlogEntry); (1)
1rende persistente l’entità myBlogEntry nel database (cioè esegue la INSERT SQL)

EntityManager::persist(2)

  • nella chiamata alla persist è verificato che non ci siano oggetti con lo stesso id nel database

  • nel caso viene sollevata una runtime exception: EntityExistsException

  • dopo la chiamata alla persist l’istanza è gestita dal PersistenceContext (managed)

Esempio di persist

  BlogEntry myBlogEntry = new BlogEntry(); // crea una istanza
  myBlogEntry.setDate(new Date());         // imposta i valori
  myBlogEntry.setTitle("Corso Java");
  myBlogEntry.setExcerpt("JPA/Hibernate");
  myBlogEntry.setBody("contenuti...");
  entityManager.persist(myBlogEntry);      // persiste l'oggetto
Table 1. blog_entry
idtitleexcerptbodydate

1

Corso Java

JPA/Hibernate

contenuti…​

2016-02-01 16:00:00

…​

EntityManager::find

Il metodo find è utilizzabile per interrogare gli entity object

public void loadBlogEntry() {
 // ...
 BlogEntry entry = entityManager.find(BlogEntry.class, myBlogId); (1)
1Carica dal database l’entity di tipo BlogEntry con id corrispondente a myBlogId (cioè effettua una SELECT)

EntityManager::find(2)

  • il metodo find accetta due paremetri

    • la classe dell’entity

    • il valore della chiave primaria

  • se non ci sono risultati per la ricerca con la chiave indicata, l’operazione restituisce null.

  • se invece ci sono, l’oggetto restituito dall’EntityManager diventa direttamente utilizzabile in Java

    • lo stato di questa istanza sarà managed

EntityManager::remove

Si usa per cancellare una istanza di entity (quindi una riga) dal database:

public void doThings() {
 // ...
 entityManager.remove(myBlogEntry); (1)
1Cancella dal database l’istanza myBlogEntry, cioè esegue una DELETE

EntityManager::remove(2)

  • la remove accetta un solo parametro entity

  • l’entityObject fornito come parametro deve essere gestito dall’entityManager

    • pena il fallimento della rimozione

  • l’operazione di cancellazione dal db può avvenire successivamente

    • dopo la EntityManager::flush() o comunque la chiusura della transazione

  • dopo la chiamata, l’entity diventerà "detached" dal entityManager e non più gestita

    • cioè sono interrotti gli automatismi di aggiornamento e di caricamento dei dati verso il db.

Entity lifecycle

Entity Lifecycle

Entity lifecycle(2)

  • le entity che sono nello stato di managed/persistent possono essere modificati dall’applicazione

  • qualsiasi modifica a queste entity è automaticamente identificata e persistita al momento in cui sarà effettuata la flush del Pesistence Context

  • non è necessario effettuare chiamate a metodi particolari per rendere queste modifiche persisitenti

  • ci possono comunque essere altre modalità di gestire le transazioni.

Ricerche JPQL

E se volessimo cercare una Person per nome o cognome? Ecco la EntityManager::createQuery

  public List<Person> byName(String firstname) {
    return entityManager
        .createQuery("SELECT p FROM Person p WHERE p.firstname = :firstname",
            Person.class)
        .setParameter("firstname", firstname).getResultList();
  }

JPQL

Java Persistence Query Language

  • è usato per definire ricerche su entità persistenti

  • è simile a SQL, ma è definito sulle entità e non sulle tabelle

  • è indipendente dal meccanismo utilizzato per immagazzinare le entity stesse.

esempio
SELECT e FROM BlogEntry e WHERE e.date >= '2016-01-01' ORDER BY title ASC

Architettura Hibernate

Architettura Hibernate