Advanced JPA

Cristian Lucchesi

Relazioni e loro caratteristiche

  • gli oggetti possono contenere uno o più riferimenti ad altri oggetti

    • questi riferimenti prendono il nome di relazioni

  • le relazioni hanno diverse caratteristiche

    • direzionalità, una relazione potrà essere:

      • unidirezionale se un oggetto fa riferimento ad un altro ma non avviene il contrario

      • bidirezionale se il riferimento tra gli oggetti è reciproco

Relazioni bidirezionali

@Entity
public class Person {
  private Account account;
}
@Entity
public class Account {
  // Possessore
  private Person owner;
}

La Person ha una relazione con Account e viceversa.

Relazioni unidirezionali

@Entity
public class Person {
  //... Altre proprietà
  private Province bornIn;
}
@Entity
public class Province {
  private String name;
}

Person ha una relazione con Province ma non viceversa.

Relazioni e opzionalità

  • altra caratteristica delle relazioni è l’opzionalità:

    • non è detto che la relazione debba esistere necessariamente.

    • se non è obbligatoria, la relazione si dice opzionale.

Relazioni e molteplicità(3)

  • la relazione può sussistere verso uno o più oggetti, in entrambe le direzioni

  • tre tipi di relazioni in funzione della molteplicità

    • uno a uno, uno a molti, molti a molti

JPA permette di gestire le relazioni e di risparmiarci di scrivere molto codice

Relazione uno a uno unidirezionale

@Entity @Table(name="persons")
public class Person {
  @OneToOne
  private Account account;
}
@Entity @Table(name="accounts")
public class Account {
  // Non c'è il campo Person
}
  • si utilizza l’annotazione @OneToOne

  • l’annotazione è solo nell’entità che possiede il riferimento (owning side)

  • la tabella persons avrà una colonna con la chiave esterna che si riferisce alla tabella accounts.

Relazione uno a uno bidirezionale

@Entity @Table(name="persons")
public class Person {
  @OneToOne
  private Account account;
}
@Entity @Table(name="accounts")
public class Account {
  @OneToOne(mappedBy="account")
  private Person person;
}
  • l’annotazione @OneToOne è su entrambe le entità (owning side e inverse side)

  • l’attributo mappedBy specifica il nome della proprietà java utilizzata nella @OneToOne che è owning side

L’importanza dell’owning side

  • il Persistence Runtime JPA (es. Hibernate) effettua aggiornamenti della relazione sul db solo quando si cambia l’owning side

@Entity @Table(name="persons")
public class Person {
  @OneToOne
  private Account account;
}
@Entity @Table(name="accounts")
public class Account {
  @OneToOne(mappedBy="account")
  private Person person;
}
...
Person p = entityManager.getReference(Person.class, 1L);
account.setPerson(p); // Non viene modificato il db

Account a = entityManager.getReference(Account.class, 1L);
person.setAccount(a); // viene aggiornato il db

Caricamento delle relazioni

Esempio di find
Person p = entityManager.find(Person.class, 1L);
query sql
select
  personwith0_.id as id1_3_1_, personwith0_.account_id as account_4_3_1_,
  personwith0_.name as name2_3_1_, personwith0_.surname as surname3_3_1_,
  account1_.id as id1_2_0_, account1_.username as username2_2_0_
from
  persons personwith0_
left outer join
  accounts account1_   on personwith0_.account_id=account1_.id
where
  personwith0_.account_id=1

L’EntityManager si occupa di caricare gli oggetti collegati con le opportune join.

Annotazione @JoinColumn

  • @JoinColumn permette di personalizzare la relazione:

    • name: (String default il nome della variabile)

      • cambia il nome della colonna sul db

    • insertable: (boolean default true)

      • se false la colonna viene omessa nella insert

    • updatable: (boolean default true)

      • se false la colonna viene omessa nella update

Annotazione @JoinColumn(2)

  • @JoinColumn permette di personalizzare la relazione:

    • nullable (boolean default true)

      • utilizzata per determinare il tipo di JOIN eseguita da jpa (INNER vs LEFT)

    • columnDefinition (String default "")

      • frammento di SQL da utilizzare per la DDL della colonna

Annotazione @JoinColumn(3)

@Entity @Table(name="persons")
public class Person {
  @OneToOne
  @JoinColumn(name="account_id", updatable = false)
  private Account account;
}
  • viene indicato il nome della colonna da utilizzare per le join e che il campo non è aggiornabile

Relazioni uno a molti

  • un’entità può essere associata a più di un oggetto dello stesso tipo

  • la relazione viene detta di tipo uno a molti.

  • analogamente alle relazioni uno ad uno può essere uni o bidirezionale

In questo caso le annotazioni utilizzate sono:

  • javax.persistence.OneToMany

  • javax.persistence.ManyToOne

Relazioni uno a molti(2)

@Entity @Table(name = "persons")
public class Person {
  @OneToMany(mappedBy = "owner")
  private List<Car> cars = new ArrayList();
}
@Entity @Table(name = "cars")
public class Car {
  @ManyToOne
  private Person owner;
}
  • una persona ha molte macchine @OneToMany

  • molte macchine hanno lo stesso (uno) proprietario @ManyToOne

Relazioni uno a molti(3)

@Entity @Table(name = "orders")
public class Order {
  @Size(min = 1)
  @OneToMany(mappedBy = "order")
  private List<LineItem> lineItems = new ArrayList<>();
}
@Entity @Table(name = "order_line_items")
public class LineItem {
  @NotNull
  @ManyToOne(optional = false)
  private Order order;
}
  • un ordine ha molte righe di dettaglio (almeno una)

  • più righe di dettaglio appartengono allo stesso ordine

  • una riga di dettaglio deve appartenere ad un ordine

Utilizzo delle relazioni

Esempio Java
Order order = entityManager.find(Order.class, 1L);
System.out.println("Ordine " + order);
for (LineItem lineItem : order.getLineItems()) {
  System.out.println(
    String.format("Riga %s. %s: %s euro",
    lineItem.getId(), lineItem.getDescription(),lineItem.getAmount()));
}
query sql
select order0_.id as id1_4_0_ from orders order0_ where order0_.id=1

select lineitems0_.order_id as order_id4_4_0_, lineitems0_.id as id1_1_0_,
  lineitems0_.id as id1_1_1_, lineitems0_.amount as amount2_1_1_,
  lineitems0_.description as descript3_1_1_,
  lineitems0_.order_id as order_id4_1_1_
from LineItem lineitems0_ where lineitems0_.order_id=1
  • non c’è bisogno di preoccuparsi delle join…​ ci pensa JPA

Relazioni uno a molti e owning side

  • il possessore della relazione owning side è sempre con @ManyToMany

  • l'inverse side è sempre con @OneToMany che è quella con l’attributo mappedBy

  • il JPA Engine tiene traccia dei cambiamenti solo sull’owning side

    • (come avviene per le relazioni @OneToOne)

Order order = entityManager.find(Order.class, 1L);
order.getLineItems().add(lineItem); // Non viene modificato il db

LineItem li = entityManager.find(LineItem.class, 1L);
li.setOrder(order); // viene aggiornato il db

Relazioni molti a molti

  • molte entità possono essere associate a molte altre entità dello stesso tipo

  • la relazione viene detta di tipo molti a molti

  • analogamente alle relazioni one-to-one e e uno-to-many può essere uni o bidirezionale

  • l’annotazione utilizzata è javax.persistence.ManyToMany

  • nelle many-to-many l'owning_side può essere a scelta su uno qualunque dei lati della relazione

Relazioni molti a molti(2)

@Entity @Table(name = "persons")
public class Person {
  @ManyToMany(mappedBy = "owners")
  private List<House> houses = new ArrayList();
}
@Entity @Table(name = "houses")
public class House {
  @ManyToMany
  private List<Person> owner = new ArrayList();
}
  • una persona può avere molte case

  • una casa può avere più proprietari

@JoinTable

  • specifica la tabella di mapping dell’associazione.

  • è applicata al owning side dell’associazione

  • di solito è utilizzata nelle many-to-many e one-to-many unidirezionali

  • se non è presente il nome della join table è la concatenazione dei nomi delle tabelle in relazione tra di loro, separati da _ (underscore) e con l'owning size prima

@ManyToMany
@JoinTable(name = "persons_houses")
private List<Person> owner = new ArrayList();

@JoinTable: joinColumns, inverseJoinColumns

@ManyToMany
@JoinTable(name = "persons_houses",
   joinColumns=  @JoinColumn(name="house_id"), (1)
   inverseJoinColumns= @JoinColumn(name="person_id") (2)
   )
private List<PersonWithAccount> owners = Lists.newArrayList();
1colonna del db relativa alla owning_side dlela relazione
2colonna del db relativa alla inverse_side della relazione

Ordine di salvataggio degli oggetti dipendenti

//...
entityManager.getTransaction().begin();
Order order = new Order();
LineItem lineItem = new LineItem();
lineItem.setOrder(order);
entityManager.persist(lineItem); (1)
entityManager.persist(order);
entityManager.getTransaction().commit();
1In questo punto l’order non è ancora salvato (non ha un id associabile in order_line_items)

WARN: HHH000437: Attempting to save one or more entities that have a non-nullable association with an unsaved transient entity. The unsaved transient entity must be saved in an operation prior to saving these dependent entities.

Cascade e Relazioni

Table Operazioni in cascata per le Entity
  • quando un’operazione è effettuata su un’entity si può scatenare la stessa operazione sulle entità collegate

CascadeDescrizione

PERSIST

quando l’oggetto è persisted, anche l’entità collegata è persisted

DETACH

quando l’oggetto è detached, anche l’entità collegata è detached

MERGE

quando l’oggetto è merged, anche l’entità collegata è merged

Cascade e Relazioni

Table 1. Table Operazioni in cascata per le Entity(cont)
CascadeDescrizione

REFRESH

quando viene effettuta la refresh dell’oggetto anche l’entità collegata viene ricaricata

REMOVE

quando viene rimosso l’oggetto anche l’entità collegata viene rimosa

ALL

tutte le operazioni sono applicate agli oggetti correlati. Equivalente a cascade={DETACH, MERGE, PERSIST, REFRESH, REMOVE}

cascade = CascadeType.*

  • Valido per tutte le relazioni @OneToOne, @OneToMany, @ManyToOne, ..

import javax.persistence.CascadeType;
//...
public class Order {
//...
  @OneToMany(mappedBy = "order", cascade = { CascadeType.PERSIST, CascadeType.REMOVE)
  private List<LineItem> lineItems = new ArrayList<>();

Esempio di CascadeType.Persist

public class Order {
  //..
  @OneToMany(mappedBy = "order", cascade = CascadeType.PERSIST)
  private List<LineItem> lineItems = new ArrayList<>();
}

//...
// utilizzo del Cascade
Order order = new Order();
LineItem lineItem = new LineItem();
lineItem.setOrder(order);
order.getLineItems().add(lineItem);
entityManager.persist(order);

L’oggetto LineItem viene salvato per effetto del
cascade = CascadeType.PERSIST

Esempio di CascadeType.REMOVE

public class Order {
  //..
  @OneToMany(mappedBy = "order", cascade = { CascadeType.PERSIST, CascadeType.REMOVE )
  private List<LineItem> lineItems = new ArrayList<>();
}

//...
// utilizzo del Cascade
Order order = entityManager.find(Order.class, 1L);
entityManager.remove(order);
Hibernate:
    select * from orders where id = 1
Hibernate:
    select * from order_line_items where order_id = 1
Hibernate:
    delete from order_line_items where id = 1
Hibernate:
    delete from orders where id = 1

Orphan Removal

  • nelle relazioni one-to-one e one-to-many quando un oggetto viene rimosso da una relazione è probabile che debba essere rimosso

  • questi oggetti sono considerati orphans e possono essere rimossi automaticamente utilizzando l’attributo orphanRemoval

  • per esempio se un Order ha molti LineItem ed un LineItem viene rimosso dall’associazione, la LineItem è considerata un orphan

  • se orphanRemoval è true, la LineItem sarà cancellata quando è rimossa l’associazione dall’ordine

  • l’attributo orphanRemoval in @OneToMany e @OneToOne accettata true/false ed il default è false

Esempio di Orphan Removal(2)

public class Order {
  @OneToMany(mappedBy = "order", orphanRemoval = true)
  public List<LineItems> getLineItems() { ... }
}
// Esempio di utilizzo
Order order = entityManager.getReference(Order.class, 11L)
order.getLineItems().clear(); (1)
1La lista viene svuotata, gli oggetti LineItem diventano orphan e vengono cancellati

Quering Entities

JPA fornisce due metodi per interrogare le Entity

  • Java Persistence query language (JPQL)

    • linguaggio semplice basasto su stringhe e simile a SQL

  • Criteria API

    • un API Java utlizzata per creare query typesafe

Java Persistence query language

  • JPQL definisce le query per interrogare le Entity ed il loro stato di persistenza

  • le query sono portabili ed indipendenti dal data store (db)

  • il linguaggio utilizza le Entity e le loro relazioni come modello di astrazione del database

    • le operazioni e le espressioni sono basate sul modello astratto

  • lo scope delle query comprende le Entity gestite dalla stessa Persistent Unit

SELECT a FROM Article a ORDER BY a.date ASC

Creazione delle Query con JPQL

EntityManager::createQuery permette di creare query dinamiche definite direttamente nella business logic dell’applicazione

@PersistenceContext
EntityManager entityManager;

public List<Person> list(int maxResults) {
  return entityManager
      .createQuery(
          "SELECT p FROM Person p", Person.class)
     .setMaxResults(maxResults)
     .getResultList();
}

@NamedQuery

  • Si possono definire query statiche con nome

    • Raccomandate perché le query usufruiscono del sistema di cache

@NamedQuery(name="ordered",  query="SELECT e FROM BlogEntry e ORDER BY e.date ASC")
public class BlogEntry {
...
public List<BlogEntry> allEntriesOrdered() {
    return entityManager.createNamedQuery("ordered", BlogEntry.class).getResultList();
}

Named Parameter

  • sono parametri della query prefissati con due punti (:).

  • sono sostituiti nella query eseguita tramite il metodo

    • javax.persistence.Query.setParameter(String name, Object value)

@PersistenceContext
EntityManager entityManager;

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

Positional Parameter

  • parametri individuati con il punto interrogativo (?) seguiti da un numero

  • sono sostituiti nella query eseguita tramite il metodo

    • javax.persistence.Query.Query.setParameter(integer position, Object value)

@PersistenceContext
EntityManager entityManager;

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

SELECT statement

  • una select query ha sei elementi

    • SELECT, FROM, WHERE, GROUP BY, HAVING e ORDER BY

  • le clausole SELECT e FROM sono obbligatorie

  • WHERE, GROUP BY, HAVING, and ORDER sono opzionali

QL_statement ::= select_clause from_clause
  [where_clause][groupby_clause][having_clause][orderby_clause]

SELECT e WHERE

  • SELECT definisce il tipo di oggetto o valore restituto

  • FROM definisce il contesto della query dichiarando uno o più variabili referenziabili nella parte SELECT e WHERE, può contenere

    • il nome astratto di un’entità

    • una collezione riferita in un’entity

    • un elemento single-valued di una relazione

  • WHERE è un’espressione condizionale che restringe gli oggetti restituiti dalla query

GROUP BY, HAVING, ORDER BY

  • GROUP BY ragrruppa i risutalti della query in accordo ad un insieme di proprietà

  • HAVING è utilizzata con la GROUP BY per restringere i risultati in accordo a delle espressioni condizionali

  • definisce l’ordinamento dei risultati

Basic Example SELECT

SELECT p FROM Player p
dati restituiti

tutti i giocatori

descrizione

la clausola FROM dichiara un variabile identificativa chiamata p, omettendo la keyword opzionale AS

Player

è l’abstract schema name dell’entity Player @Entity class Player {…​}

Eliminare i duplicati

SELECT DISTINCT p FROM Player p
  WHERE p.position = :position AND p.name = :name
dati restituti

i giocatori con una determinata posizione e nome

descrizione
  • position e name sono campi persistenti dell’entità Player.

  • nella WHERE si compare il valore dei campi nel db con quelli passati per parametro.

  • *DISTINCT" elimina eventuali duplicati

Simple Query con relazioni

SELECT DISTINCT p
  FROM Player p JOIN p.teams t
dati restituiti

tutti i giocatori che appartengono almeno ad una squadra

descrizione
  • p rappresenta l’entità Player, t l’entità Team correlata

  • p.teams naviga da un Player ai suoi Team correlati.

  • il punto (.) nell’espressione p.teams è l’operatore di navigazione

Simple Query con relazioni(2)

SELECT DISTINCT p
  FROM Player p JOIN p.teams t

è equivalente a

SELECT DISTINCT p
  FROM Player p
  WHERE p.team IS NOT EMPTY

ed a

SELECT DISTINCT p
  FROM Player p, IN (p.teams) t

Attraversare le relazioni

SELECT DISTINCT p
  FROM Player p JOIN p.teams
  WHERE t.city = :city
dati restituiti

tutti i giocatori che appartengono ad una squadra di una determinata città

ATTENZIONE: nelle WHERE non si possono navigare le collezioni, non si può scrivere WHERE t.teams.city = :city (illegal expression)

Altre espressioni condizionali

Like Expression
SELECT p FROM Player p
  WHERE p.name LIKE 'Mich%'
Null Expression
SELECT t FROM Team t
  WHERE t.league IS NULL
IS Empty Expression
SELECT p FROM Player p
  WHERE p.teams IS EMPTY

Altre espres. condizionali(2)

BETWEEN Expression
SELECT DISTINCT p FROM Player p
  WHERE p.salary BETWEEN :lowerSalary AND :higherSalary
Operatori di comparazione
SELECT DISTINCT p1
  FROM Player p1, Player p2
  WHERE p1.salary > p2.salary AND p2.name = :name

Uguaglianza nelle condizioni

public List<Person> havingHouse(House house) {
  return entityManager.createQuery(
      "SELECT p FROM Person JOIN p.houses h"
      + " WHERE h = :house", Person.class)
  .setParameter("house", house)
  .getResultList();
}
h = h:house

Compara gli oggetti di tipo House relazionati a Person con l’oggetto House passato per parametro

Semantica dell’uguaglianza per le Entity

Due entity dello stesso abstract schema type sono considerate uguali se e solo se le loro chiavi primarie hanno lo stesso valore

ORDER BY

SELECT o
  FROM Customer c JOIN c.orders o JOIN c.address a
  WHERE a.state = 'CA'
  ORDER BY o.quantity, o.totalcost

GROUP BY e HAVING

SELECT c.status, AVG(o.totalPrice)
  FROM CustomerOrder o JOIN o.customer c
  GROUP BY c.status HAVING c.status IN (1, 2, 3)

ALTRE FUNZIONI JPQL

UPDATE e DELETE

Gli statemente Update e delete fornisco operazioni su un insieme di entity.

update_statement :: = update_clause [where_clause]
delete_statement :: = delete_clause [where_clause]
  • operano in accordo alle condizioni impostate nella WHERE

  • la clausola WHERE segue le stesse regole valide per la SELECT

Esempio Bulk Update

UPDATE Player p
  SET p.status = 'inactive'
  WHERE p.lastPlayed < :inactiveThresholdDate
Descrizione

Imposta a inactive tutti i giocatori che non giocano da molto tempo

Esempio Bulk Delete

DELETE FROM Player p
  WHERE p.status = 'inactive'
  AND p.teams IS EMPTY
Descrizione

Cancella tutti i giocatori inattivi che non appartengono a nessun Team

Criteria API

  • sono una alternativa in JPA 2.x per a JPQL

  • le query sono dinamiche

  • si compongono a partire da oggetti Java standard

    • le query sono create in modo typesafe

  • il CriteriaBuidler si ottiene utilizzando il metodo EntityManager::getCriteriaBuilder

Esempio query con Criteria API

String-based Query
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<BlogEntry> criteriaQuery = builder.createQuery(BlogEntry.class);
Root<BlogEntry> be = criteriaQuery.from(BlogEntry.class);
ParameterExpression<String> title = builder.parameter(String.Class);
criteriaQuery.select(be).where(builder.equals(be.get("title"), title);

TypedQuery<BlogEntry> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setParameter(parameter, "il mio titolo");
typedQuery.getSingleResult(); // recupera il blogentry con "il mio titolo"

Criteria API ≈ JPQL

  • Criteria API e JPQL sono strettamente legate e con operatori analoghi nelle loro query.

  • gli sviluppatori familiari con la sintassi JPQL troverranno l’equivalente operazioni object-level nella Criteria API.

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet);
TypedQuery<Pet> q = em.createQuery(cq);
List<Pet> allPets = q.getResultList();

è equivalente a

List<Pet> allPets =
  entityManager.createQuery("SELECT p FROM Pet p", Pet.class).getResultList();

Metamodel API

  • JPA2 prevede l’utilizzo di un Metamodel per ogni entity per garantire il typesafe delle query.

  • un Metamodel è una classe i cui attributi corrispondono ai campi persistenti ed alle relazioni dell’entity.

  • di solito la classe del Metamodel ha lo stesso il nome con un underscore (_) finale

Esempio Metamodel

@Entity
public class Pet {
    @Id
    protected Long id;
    protected String name;
    protected String color;
    @ManyToOne
    protected Set<Person> owners;
    ...
}
@Static Metamodel(Pet.class)
public class Pet_ {
    public static volatile SingularAttribute<Pet, Long> id;
    public static volatile SingularAttribute<Pet, String> name;
    public static volatile SingularAttribute<Pet, String> color;
    public static volatile SetAttribute<Pet, Person> owners;
}

Esempi utilizzo Metamodel

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.where(cb.equal(pet.get(Pet_.name), "Fido"));
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.where(pet.get(Pet_.color).isNull());
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.where(pet.get(Pet_.color).in("brown", "black"));

Generare il Metamodel

<plugin>
    <groupId>org.bsc.maven</groupId>
    <artifactId>maven-processor-plugin</artifactId>
    <executions>
        <execution>
            <id>process</id>
            <goals>
                <goal>process</goal>
            </goals>
            <phase>generate-sources</phase>
            <configuration>
                <!-- source output directory -->
                <outputDirectory>target/metamodel</outputDirectory>
            </configuration>
        </execution>
    </executions>
    <dependencies>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-jpamodelgen</artifactId>
        <version>4.3.11.Final</version>
      </dependency>
    </dependencies>
</plugin>
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>1.3</version>
    <executions>
        <execution>
            <id>add-source</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>add-source</goal>
            </goals>
            <configuration>
                <sources>
                    <source>target/metamodel</source>
                </sources>
            </configuration>
        </execution>
    </executions>
</plugin>

Criteria & Join

public List<Pet> byOwnerName(String ownerName) {
  CriteriaBuilder cb = entityManager.getCriteriaBuilder();
  CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);

  Root<Pet> pet = cq.from(Pet.class);
  Join<Pet, Owner> owner = pet.join(Pet_.owners);
  cq.select(pet).where(cb.equal(owner.get(Owner_.name), ownerName));

  return entityManager.createQuery(criteriaQuery.where()).getResultList();
}

Comporre i predicati

  • I predicati sono componibili con: and, or, not

CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.where(cb.equal(pet.get(Pet_.name), "Fido")
    .and(cb.equal(pet.get(Pet_.color), "brown")));

Ordinare i risultati

CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet);
cq.orderBy(cb.desc(pet.get(Pet_.birthday)));
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
Join<Owner, Address> address = pet.join(Pet_.owners).join(Owner_.address);
cq.select(pet);
cq.orderBy(cb.asc(address.get(Address_.postalCode)));
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
Join<Pet, Owner> owner = pet.join(Pet_.owners);
cq.select(pet);
cq.orderBy(cb.asc(owner.get(Owner_.lastName)), owner.get(Owner_.firstName)));

GroupBy & Having

CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.groupBy(pet.get(Pet_.color));
cq.having(cb.in(pet.get(Pet_.color)).value("brown").value("blonde"));
Descrizione

Restituisce la lista di Pet il cui Owner si chiama come il parametro ownerName passato

JPQL vs Criteria Query

  • Vantaggi JPQL

    • le query JPQL sono poche righe, coincise e più leggibili

    • gli sviluppatori già familiari con SQL le imparano velocemente

    • le JPQL NamedQuery possono essere definite e riutilizzate facilmente

JPQL vs Criteria Query(2)

  • Svantaggi JPQL

    • JPQL query non sono typesafe

    • richiedono un cast quando si leggono i risultati dall’entityManager

    • sono sottoposte a problemi di type-casting non intercettabili a compile time

    • in caso di refactoring delle Entity non c’è nessun controllo sulle stringhe JPQL utilizzate

JPQL vs Criteria Query(3)

  • Vantaggi Criteria Query

    • permettono di definire le query a livello applicativo con oggetti riutlizzabili

    • hanno migliori performance perché non necessitano il parsing della query (String) ogni volta

    • sono typesafe e non richiedono type casting

    • sono un API Java e non richiedono di imparare un nuovo linguaggio

    • in caso di rifattorizzazione le query sono parzialmente rifattorizzate automaticamente

JPQL vs Criteria Query(4)

  • Svantaggi di Criteria Query

    • sono più prolisse delle query JPQL

    • richiedono la creazione di molti oggetti ed eseguire diversi metodi per sottomettere le query

Implementazioni JPA

Esistono varie implementazioni JPA che tipicamente sono utilizzati all’interno di application server:

  • Hibernate, in JBoss/RedHat

  • EclipseLink, Oracle

  • OpenJPA …​

Utilizzare JPQL in Spring

  • org.springframework.data.jpa.repository.Query

public interface ArticleDao extends PagingAndSortingRepository<Article, Integer> {
  @Query("SELECT a FROM Article a WHERE a.author.surname = ?1")
  List<Article> findByAuthorSurname(String surname);
}

Evoluzione del database

Riferimenti ed approfondimenti