Ejb3 In Action Cap 10

Nel cap 8 si vede come mappare le entità in tabelle , nel 9 come usare l'entity manager, in questo capitolo si vedono le query.
Per recuperate le entità vi sono quindi tre metodi

  1. con l'entity manager trovando l'entità con la chiave primaria
  2. tramite query jpql
  3. tramite query in sql nativo

10.1 Introducing the query API

Vi sono due tipi di query dinamic e named

10.1.1 The big picture

Dobbiamo preferire le query in JPQL dette anche in JPA perchè le SQL query ritornano database records mentre le JPA query restituiscono entità.
Le query si eseguono attraverso l'entity manager che nasconde il collegamento diretto al db al contrario di come si faceva usando JDBC.
Ecco un paragone tra i passi dei due metodi:

Basic Steps for JDBC Query Using SQL Basic Steps for a JPA Query Using JPQL
1. Obtain a database connection. 1. Obtain an instance of an entity manager.
2. Create a query statement. 2. Create a query instance.
3. Execute the statement. 3. Execute the query.
4. Retrieve the results (database records). 4. Retrieve the results (entities).

10.1.2 Anatomy of a query

L'api della query supporta due tipi named e dynamic.
Named sono usate per quando si deve memorizzare e riusare. Se la stessa query deve essere eseguita da più punti nell'applicazione viene costruita una sola volta.
Se invece la query cambia in base alla logica dell'applicazione per lo stato interno o per gli input dell'utente disogna usare le query dinamiche o ad hoc che vengono create al volo.
In entrambi i casi la dinamica della query è la stessa

@PersistenceContext em;
...
public List findAllCategories() {
  Query query = em.createQuery("SELECT c FROM Category c"); ...
   return query.getResultList(); ............
}

In questo esempio si è usata una query dinamica, l'unica differenza sta nel metodo chiamato createNamedQuery per le named piuttosto che createQuery per le dinamiche.
Abbiamo usato JPQL ma anche con SQL nativo si possono usare entrambi i tipi di query.

10.1.3 Defining named queries

È possibile creare una query named (detta anche statica) prima di utilizzarla usando le annotazioni o l'xml (questo si vede nel capitolo 11). A una query named si accede tramite il suo nome.
Ha tre benefici:

  1. migliora la riusabilità della query
  2. migliora la possibilità di manutenzione del codice, le query non sono spezzate nella buisness logic
  3. migliorano le performance perchè sono preparate una volta sola e riusate

La query può essere memorizzata anche in un descrittore xml orm ma ora vedremo quella nel codice.
Per fare questo useremo l'annotazione @javax.persistence.Named-
Query

@Entity
@NamedQuery(
   name = "findAllCategories",
  query = "SELECT c FROM Category c WHERE c.categoryName
              LIKE :categoryName ")
public class Category implements Serializable {
..
}

In un'applicazione complessa si possono avere più di una NamedQuery quindi si usa l'annotazione @javax.persistence.NamedQueries
Qui vi è l'esempio

@Entity
@NamedQueries({
  @NamedQuery(
     name = "findCategoryByName",
     query = "SELECT c FROM Category c WHERE c.categoryName
              LIKE :categoryName order by c.categoryId"
  ),
  @NamedQuery(
     name = "findCategoryByUser",
     query = "SELECT c FROM Category c JOIN c.user u
              WHERE u.userId = ?1"
)})
@Table(name = "CATEGORIES")
public class Category implements Serializable {
}

Bisogna dare nomi univoci alla query nell'applicazione.

10.2 Executing the queries

Eseguire una query in EJB è simile a Hibernate or TopLink, i passi sono:

  1. Create an instance of the EntityManager.
  2. Create an instance of the query.
  3. Execute the query.

Il primo punto si è visto nel capitolo 9.
L'entity manager fornisce diversi metodi per creare query o query in sql nativo.

Method Purpose
public Query createQuery(String qlString); Creates a dynamic query using a JPQL statement.
public Query createNamedQuery(String name); Creates a query instance based on a named query. This method can be used for both JPQL and native SQL queries.
public Query createNativeQuery(native SQL statement with UPDATE String sqlString); Creates a dynamic query using a or DELETE.
public Query createNativeQuery(native SQL statement that retrieves a String sqlString,Class result-class); Creates a dynamic query using a single entity type.
public Query createNativeQuery(native SQL statement that retrieves a String sqlString,String result-setMapping); Creates a dynamic query using a result set with multiple entity types.

È consigliato urare le query native solo come ultima risorsa.
Non è necessaria una transazione attiva per eseguire una query ci pensa da solo.

Creating a named query instance

Per creare una named query bisogna avere una istanza di entity manager aperta. Basta usare il metodo specifico passando il nome della named query ecco l'esempio

Query query = em.createNamedQuery("findAllCategories");

Creating a dynamic query instance

Per queste query deve essere Entity Manager disponibile questo vuol dire che deve essere in un session beans, MDBs, web applications, o persino fuori container.

Query query = em.createQuery("SELECT i FROM Item i");

10.2.2 Working with the Query interface

Vi sono diversi metodi parametrizzabili per query. Non vi sono differenze per passare JPQL o SQL nativo in Query però non nell'entity.
Ecco nell'immagine i metodi disponibili:

metody-di-Query.xcf

Un esempio di esecuzione

query = em.createNamedQuery("findCategoryByName");
query.setParameter("categoryName", categoryName);
query.setMaxResults(10);
query.setFirstResult(3);
List categories = query.getResultList();

Si è usato una query predefinita, si è impostato il parametro, si è chiesto di ottenere massimo 10 risultati, e bisogna iniziare a mostrare i risultati dal campo 3 chissà che vuol dire.
Da pag 349 a 351 vi sono tutti i parametri specifici della query.

Setting parameters for a query

Ecco la query di cui si vuole specificare il parametro

SELECT i FROM Item i WHERE i.initialPrice = ?1
query.setParameter(1, 100.00);

Questo metodo il posizionale è però sconsigliato era unsato in EJB2 conviene usare quello per nome
SELECT i FROM Item i WHERE i.initialPrice = :price
query.setParameter("price", 100.00);

Retrieving a single entity

Questa query si usa quando siamo sicuri che ci sia una sola entità che corrisponda ai nostri parametri.

query.setParameter(1, "Recycle from Mr. Dumpster");
Category cat = (Category)query.getSingleResult();

Usando questo metodo si possono avere NonUniqueResultException o NoResultException
Le eccezioni devono essere catturare così
try {
  ...
  query.setParameter(1, "Recycle from Mr. Dumpster");
  Category cat = (Category)query.getSingleResult();
  ...
}catch (NonUniqueResultException ex) {
  handleException(ex);
}
catch (NoResultException ex) {
  handleException(ex);
}

Non serve una transazione attiva se non ci sono altre attività la connessione all'entity manager viene chiusa dopo l'uso.
Si avrà una IllegalStateException if Query contains an UPDATE or DELETE statement.

Retrieving a collection of entities

Questa restituisce una collezione di entità

query.setParameter("lowPrice", lowPriceValue)
query.setParameter("highPrice", highPriceValue)
List items = query.getResultList();

Se la query da risultato vuoto restituirà una lista vuota.
Vale lo stesso discorso per le transazioni visto prima, non ne è richiesta una attiva e dopo l'uso avviene il detach.

Paginating through a result list

Vi possono essere risultati di molti campi.
Se si vuole caricare una query in maniera incrementale si possono usare i seguenti parametri

query.setMaxResults(100);
query.setFirstResult(200);
List items = query.getResultList();

Massimo 100 risultati per pagina e inizia a mostrare dall'item 200.

Controlling the query flush mode

La modalità di flusso predefinita è AUTO questa indica che tutti gli aggiornamenti devono essere gestiti da contesto di persistenza. Si può impostare anche FlushModeType.COMMIT in questo modo il comportamento sugli aggiornamenti non è definito, prestare attenzione a questo cambiamento.

10.2.3 Specifying query hints

Sono delle opzioni specificate in base al tipo di sistema che ci sia sotto (leggi marca di database) vi sono i dettagli in questo paragrafo ma per ora non mi interessano.

10.3 Introducing JPQL

JPQL è un estensione del linguaggio usato nelle EJB2. JPQL opera sulle entità mentre SQL opera sulle tabelle. Vi è il JPQL Query processor che traduce da JPQL a SQL standard.
Somiglia molto al SLQ standard quindi ci si può confondere.

10.3.1 Defining statement types

Defining and using SELECT

SELECT c
FROM Category c
WHERE c.categoryName LIKE :categoryName
ORDER BY c.categoryId

Using UPDATE

UPDATE Seller s
SET s.status = 'G', s.commissionRate = 10
WHERE s.lastName like 'PackRat%'

Using DELETE

DELETE Seller s
WHERE s.status = 'Silver'

10.3.2 Using the FROM clause

Identifying the query domain: naming an entity

Il nome viene specificato nell'annotazione Entity, se non viene indicato li prende il nome della classe, ovviamente i nomi devono essere univoci.

Identifier variables

L'alias si può specificare tramite AS oppure senza niente.

FROM entityName [AS] identificationVariable

Non deve essere una parola riservata di JPQL e neanche una Entity già presente nel sistema.

What is a path expression?

c.categoryName è una path expression questa può essere o un oggetto singolo oppure una collection. I campi di associazione che rappresentano una oneToMany o una ManyToMany sono collezioni.
Per esempio

SELECT distinct c
FROM Category c
WHERE c.items is NOT EMPTY

c.items è una collection
Si può usare anche una versione annidata delle espressioni c.items.user.firstName.
Non si può navigare in un valore della collection.

Filtering with WHERE

SELECT c
FROM Category c
WHERE c.categoryId > 500

Si può usare in una where clausole boolean, float, enum, String, int non si possono usare octal and hexadecimals, non si possono usare tipi come byte[] or char[].
Non bisogna dimenticarsi che le dichiarazioni JPQL vengano tradotte in SQL.
Attualmente l'SQL impone che i tipi BLOB e CLOB non possono essere usati in una WHERE clause.

Passing parameters: positional and named

Si possono passare parametri più complessi di un int o una String. The parameter
can take more complex types, such as another entity type; however, you are limited
to using conditional expressions that involve a single-value path expression.

10.3.3 Conditional expressions and operators

Si possono usare espressioni:
Unary sign: +, -
Arithmetic: *, /,+, -
Relational: =, >, >=, <, <=, <>, [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY, [NOT] MEMBER [OF]
Logical: NOT, AND,OR

WHERE c.categoryName = 'Dumped Cars' 
    OR c.categoryName = 'Furniture from Garbage'

Gli operatori String and Boolean possono uesare: equality (=) e nonequality (<>).

Using a range with BETWEEN

Per confronti tra range di valori

WHERE c.categoryId BETWEEN :lowRange AND :highRange

Si può usare con valori arithmetic, string, or DATETIME.

Using the IN operator

L'espressione deve esistere in una lista di valori.

WHERE u.userId IN ('viper', 'drdba', 'dumpster')

La lista può essere statica oppure restituita da una lista di valori.
WHERE c.user IN (SELECT u
                 FROM User u
                 WHERE u.userType = 'A')

Using the LIKE operator

Quando una espressione di tipo stringa matcha un path stringa.

string_value_path_expression [NOT] LIKE pattern_value_

Il pattern value può contenere "_" o "%" . L' _ sta per un carattere singolo, mentre % sta per qualsiasi range di caratteri.
WHERE c.categoryName LIKE 'Recycle%'

Dealing with null values and empty collections

Il valore null che si incontra spesso all'interno di un db è diverso dal valore stringa vuota. JPQL lo tratta in maniera diversa ma non tutti i db fanno lo stesso.
Se l'sql restituisce true quando confronta una stringa vuota con un null si potrebbero avere dei risultati inconsistenti.
Nella tabella 10.9 vi è il risultato di espressioni che contengono null con operatori diversi.

valori-null.jpg

Si può avere il controllo su un espressione

WHERE c.parentCategory IS NOT NULL

Ma non si può fare un IS NULL su espressioni che restituiscono una collection. In altre parole con l'operatore IS NULL non si può determinare se una collezione è vuota o meno. Per questo tipo di controllo vi è un altro tipo di controllo.
WHERE c.items IS EMPTY

Dato che questo tipo di controllo in SQL non esiste viene generato una query con delle JOIN per effetturare il controllo.
Ecco qui l'esempio della query in JPQL
SELECT c
FROM Category c
WHERE c.items IS EMPTY

e del codice sql generato di conseguenza.
SELECT
  c.CATEGORY_ID, c.CATEGORY_NAME, c.CREATE_DATE,
  c.CREATED_BY, c.PARENT_ID
FROM CATEGORIES c
WHERE (
  (SELECT COUNT(*)
  FROM CATEGORIES_ITEMS ci, ITEMS i
  WHERE (
    (ci.CATEGORY_ID = c.CATEGORY_ID) AND
    (i.ITEM_ID = ci.ITEM_ID))) = 0)

Checking for the existence of an entity in a collection

Per verificare se un membro fa parte (o non fa parte) della collection

entity_expression [NOT] MEMBER [OF] collection_value_path_expression
WHERE :item MEMBER OF c.items

10.3.4 Working with JPQL functions

String functions

Vi sono delle funzioni per le stringhe che possono essere utilizzate.

funzioni-stringa1.jpg
funzioni-stringa2.jpg
WHERE CONCAT(u.firstName, u.lastName) = 'ViperAdmin'

Queste funzioni sono da usare con parsimonia sole se servono perché le funzioni in java dalla memoria sono più veloci rispetto a quelle usate nel db.

Arithmetic functions

Di solito non si usano per le operazioni di CRUD ma alcune volte possono essere utili.

WHERE SIZE(c.items) = 5

funzioni-aritmetiche.jpg

Temporal functions

Le funzioni temporarli sono calcolate con il tempo del db quindi attenzione perchè potrebbe essere diverso da quello della jvm se non operano sullo stesso server.
funzioni-temporali.jpg

10.3.5 Using a SELECT clause

Using a constructor expression in a SELECT clause

Si possono creare delle istanze nella SELECT che poi vengono restituite. Questo è utile quando si vogliono creare istanze in una query inizializzate con valori di una sottoquery.

SELECT NEW actionbazaar.persistence.ItemReport (c.categoryID, c.createdBy)
FROM Category
WHERE categoryId.createdBy = :userName

La classe specifica non deve essere mappata nel db e non è necessario che sia una Entity.
Polymorphic queries

JPA supporta il polimorfismo e le query JPQL sono polimorfiche…..
va be lasciamo perdere per ora questo quadrationo.

10.3.6 Using aggregations

Aggregate functions

funzioni-di-aggregazione.jpg

Eccone un esempio

SELECT MAX(i.itemPrice)
FROM Item i

Grouping with GROUP BY and HAVING

La query più semplice è

SELECT c.user, COUNT(c.categoryId)
FROM Category c
GROUP BY c.user

Si può usare per avere un ulteriore controllo anche l'HAVING.
SELECT c.user, COUNT(c.categoryId)
FROM Category c
GROUP BY c.user
HAVING COUNT(c.categoryId) > 5

Oppure con un controllo ancora maggiore qualcosa del genere.
SELECT c.user, COUNT(c.categoryId)
FROM Category c
WHERE c.createDate is BETWEEN :date1 and :date2
GROUP BY c.user
HAVING COUNT(c.categoryId) > 5

Prima viene valutata la WHERE poi la GROUP BY e infine la HAVING

10.3.7 Ordering the query result

Si può ordinare il risultato, in maniera ASCendente o DESC decrescente, se non si specifica niente il default è ASC.

SELECT c
FROM Category c
ORDER BY c.categoryName ASC

La sintassi stretta è
ORDER BY path_expression1 [ASC | DESC], ... path_expressionN  [ASC | DESC]

Si possono usare espressioni composte
SELECT c.categoryName, c.createDate
FROM Category c
ORDER BY c.categoryName ASC, c.createDate DESC

10.3.8 Using subqueries

Al contrario dell'SQL in JPQL le sottoquery non sono supportate nel campo FROM.
La subquery viene valutata sempre prima della query perché quest'ultima ne utilizza i risultati. Si possono usare queste condizioni con le sotto query

[NOT] IN / [NOT] EXISTS / ALL / ANY / SOME (subquery)

Using IN with a subquery

Ecco un esempio di utilizzo di una subquery con IN

SELECT i
FROM Item i
WHERE i.user IN (SELECT c.user
                 FROM Category c
                 WHERE c.categoryName LIKE :name)

EXISTS

Per vedere se ne esiste almeno una entità. EXISTS è per i result set mentre IN per un espressione di valutazione singola.
Restituisce true se ne esiste almeno uno altrimenti false.
Questa query se non capisco male restituisce le istanze di Item che abbiano almeno una categoria.

SELECT i
FROM Item i
WHERE EXISTS (SELECT c
              FROM Category c
              WHERE c.user = i.user)

Usare EXISTS è preferibile rispetto ad IN, in particolare quando la tabella contiene un largo numero di Record, perchè con l'EXISTS il db ha performance migliori.
"Again, this is due to the work of the query processor translating JPQL queries into SQL by the persistence provider." Che vorrà dire che per il traduttore la traduzione con l'EXISTS è più difficile rispetto all'IN?

ANY, ALL, and SOME

Questi operatori sono simili rispetto all'IN si possono usare con operazioni di confronto numeriche =, >, >=,
<, <= and <>
Ecco un esempio con ALL, la WHERE darà valore true se tutti gli elementi della SELECT soddisfano la condizione.

SELECT c
FROM Category c
WHERE c.createDate >= ALL
                (SELECT i.createDate
                 FROM Item i
                 WHERE i.user = c.user)

SOME è un sinonimo di ANY indicano se almeno un elemento soddisfa la condizione (se non sbaglio).
Ecco un esempio di ANY
SELECT c
FROM Category c
WHERE c.createDate >= ANY
                (SELECT i.createDate
                 FROM Item i
                 WHERE i.seller = c.user)

10.3.9 Joining entities

Con l'operatore JOIN si crea un prodotto cartesiano si usa insieme alla WHERE per selezionare quello che ci serve. La JOIN si specifica nella clausola FROM.
Facciamo l'esempio di avere due entità Category e Item. Ci sono diversi tipi di join:

  • inner join. Risultati che collegano Category e Item.
  • outer join. Otteniamo risultati che soddisfano la JOIN ma anche che includono entità del dominio da un solo lato della relazione. Per esempio vogliamo ritrovare tutte le istanze di Category che non matchano istanze di Item. Ci sono delle outer join di tipo left, right o entrambe

Theta-joins

Le theta joins sono meno comuni si basano su campi che entrambi le entità possiedono. Ma non sono campi sistematici come quelle relazioni con chiavi esterne dove c'è il collegamento definito formalmente. Possono essere campi con nomi diversi che hanno dei valori uguali. Ecco un esempio

SELECT i FROM Item i, Category c
WHERE i.star = c.rating

In questo caso entrambi i campi possono avere uno di questi 4 valori DELUXE, GOLD, STANDARD, PREMIUM.

Relationship joins

Ecco la sintassi di una JOIN

[INNER] JOIN join_association_path_expression [AS]
  identification_variable

Come si può vedere la join di default è la INNER
Qui vi è una join con relazioni uno a molti tra User e Category
SELECT u
FROM User u INNER JOIN u.Category c
WHERE u.userId LIKE ?1

Outer joins

Qui vi è una relazione che esegue la join sulla relazione User e Category, nel caso di User che non hanno Category viene visualizzato NULL. Grazie alla outer join

SELECT u
FROM User u LEFT OUTER JOIN u.Category c
WHERE u.userId like ?1

Fetch joins

È tipico di una applicazione dover recuperare una entità con tutte le associazioni ad altre entità ad essa collegata, questo in una sola operazione. Questo è utile nel caso di lazy loading, si può usare sia per inner che per outer join.
Nell'esempio qui sotto recuperioamo una entità Bid dal sistema e vogliamo caricare e inizializzare le istanze associate di Bidder

SELECT b
FROM Bid b JOIN FETCH b.bidder
WHERE b.bidDate >= :bidDate

Ecco un esempio di query con caricamento di doppia entità
SELECT cg FROM ContactGroup cg 
JOIN FETCH cg.contacts
JOIN FETCH cg.childrenContactGroups
WHERE cg.contactGroupId = :idSetted

ATTENZIONE SUL LIBRO VI È L'ESEMPIO CON SCRITTO FETCH JOIN CHE DA ERRORE PERCHÈ LA SINTASSI È JOIN FETCH.

10.3.10 Bulk updates and deletes

Questa è una tipica operazione di aggiornamento

UPDATE User u
SET u.status = 'G'
WHERE u.numTrades >=?1

Eccoinvece un esempio di utilizzo di query di modifica
@PersistenceContext em;
. . .
// start transaction
Query query = em.createQuery("DELETE USER u WHERE u.status = :status ");
query.setParameter("status", 'GOLD');
int results = query.executeUpdate();
//end transaction

Rispetto all'uso comune abbiamo un utilizzo del metodo executeUpdate , inoltre lo invocihiamo con una transazione attiva.
Perchè le bulk updates e deletes implicano alcuni "trabbochetti" è raccomandabile isolare ogni operazione di bulk in una transazione discreta. Questo perchè sono direttamente convertite in operazioni di database e possono causare inconsistenza tra entità managed e database.

10.4 Native SQL queries

Bisogna fare attenzione ad usare le query native questo oltre ai problemi di mancata risoluzione delle applicazioni comporta una minore portabilità dell'applicazione se si decide di cambiare il database che si trova sotto.

Un motivo per usare le query native e per esempio l'uso di sotto-categorie. JPQL non supporta join ricorsive.

Bisogna evitare di usare INSER, DELETE, UPDATE per il JPA non tiene conto di queste modifiche e quindi questo potrebbe portare ad avere delle inconsistenze a causa delle operazioni automatiche che il JPA esegue sui bean.

Non si deve dimenticare che JPQL restituisce istanze di oggetti mentre SQL restituisce records di database.

Come in JPQL, si possono usare entrambe dynamic queries e named queries con SQL (capire la differenza tra queste due query).

10.4.1 Using dynamic queries with native SQL

Il metodo usato per create una query nativa è createNativeQuery, ecco un esempio

Query q = em.createNativeQuery("SELECT user_id, first_name, last_name "
          + " FROM users WHERE user_id IN (SELECT seller_id FROM "
          + "items GROUP BY seller_id HAVING COUNT(*) > 1)",
            actionbazaar.persistence.User.class);
return q.getResultList();

Il metodo prende due parametri la query e la classe restituita. Diventa un problema se la query restituisce più di una entità di classe, per la quale JPA permette a @SqlResultSetMapping che viene usato con createNativeQuery invece di passare una entity class. Un @SqlResultSetMapping può essere mappato su una o più entità. Ecco un esempio.
@SqlResultSetMapping(name = "UserResults",
  entities = @EntityResult(
    entityClass = actionbazaar.persistence.User.class))

Poi si può specificare il mapping sulla query
Query q = em.createNativeQuery("SELECT user_id, first_name, last_name "
          + " FROM users WHERE user_id IN (SELECT seller_id FROM "
          + "items GROUP BY seller_id HAVING COUNT(*) > 1)",
            "UserResults");
return q.getResultList();

In questo modo le entità vengono determinate attraverso il @SqlResultSetMapping e anche nel caso di più entità no vi sono problemi avviene tutto come una JPQL query.

10.4.2 Using a named native SQL query

Usare una named native query è simile per le named JPQL query. Per usarla prima bisogna crearla. Si può usare l'annotazione @NamedNativeQuery che è definita in questo modo

  public @interface NamedNativeQuery {
    String name();
    String query();
    QueryHint[] hints() default {};
    Class resultClass() default void.class;
    String resultSetMapping() default ""; // name of SQLResultSetMapping
  }

Si può usare una classe di tipo entità o un mapping su result set con l'annotazione NamedNativeQuery. Supponiamo di voler convertire la query usata precedentemente in una named native query. Il primo passo è definire la named native query nell'entità User
@NamedNativeQuery(
  name = "findUserWithMoreItems",
  query = "SELECT user_id , first_name , last_name,
              birth_date
   FROM   users
   WHERE user_id IN
  ( SELECT seller_id
   FROM items
   GROUP BY seller_id   HAVING COUNT(*) > ?)",
   hints = {@QueryHint(name = "toplink.cache-usage",
      value="DoNotCheckCache")},
  resultClass = actionbazaar.persistence.User.class)

Se vogliamo che restituisca più di una entity calls dobbiamo definire SqlResultSetMapping in questo modo
@NamedNativeQuery(
  name = "findUserWithMoreItems",
  query = "SELECT user_id , first_name , last_name, birth_date
          FROM users 
          WHERE user_id IN
            (SELECT seller_id  FROM items
             GROUP BY seller_id
             HAVING COUNT(*) > ?)",
  resultSetMapping = "UserResults")

Non ci sono differenze tra eseguire una named native query in SQL o in JPQL , eccetto per il fatto che un parametro named in SQL nativo non è richiesto nelle JPA spec.
Per dimostrare come è simile eseguire JPQL e SQL eseguiamo un metodo definito precedentemente.
return em.createNamedQuery("findUserWithMoreItems").setParameter(1, 5).getResultList();

JPA and database stored procedures

In sql è possibile utilizzare la potenza delle query di database di tipo stored procedures. In JPA non sono permesse perchè dipendono dalle caratteristiche delle persistence provider. Certamente è possibile utilizzare semplici stored functions (senza parametri di tipo out ) con una query SQL nativa.

Fuori dal libro query interessanti

Recuperare una lista passando gli id

public List<ContactGroup> findByMultpleIds(Long[] ids) throws RecordNotFoundException
    {
        List<ContactGroup> groups;
 
        if ((ids != null) && (ids.length > 0))
        {
            String interString = "SELECT cg " + "FROM ContactGroup cg " + "WHERE cg.contactGroupId IN (";
 
            for (int i = 0; i < ids.length; i++)
            {
                interString += "?" + (i + 1) + ",";
            }
            // delete the last character
            interString = interString.substring(0, interString.length() - 1);
 
            interString += ")";
 
            try
            {
                Query query;
 
                query = entityManager.createQuery(interString);
                for (int i = 0; i < ids.length; i++)
                {
                    query.setParameter(i + 1, ids[i]);
                }
 
                groups = query.getResultList();
            }
            catch (RuntimeException re)
            {
                LogUtil.log("find by user failed", Level.SEVERE, re);
                throw re;
            }
        }
        else
            groups = new ArrayList<ContactGroup>();
 
        return groups;
    }

Risolvere una relazione molti a molti

In questo caso la relazione è definita nella dichiarazione dell'entità ma essendo lazy bisogna recuperarla con una query. Come si vede la query è semplice ed intuitiva e richiamando la struttura java (come vuole jpql) viene abbastanza immediato risolverla.

"SELECT g.childrenContactGroups FROM ContactGroup g WHERE g.contactGroupId = :groupId"

JOIN TRA TABELLE

Qui vi è un esempio di JOIN di tipo LEFT in una relazione molti a molti già definita nelle entità

SELECT c FROM Contact c 
LEFT JOIN FETCH c.contactGroups 
WHERE c.contactId = :contactId

Sotto invece si fa una join senza usare le facility di JPQL e le relazioni già definite nelle tabelle.

SELECT Contact.ContactId FROM ContactGroup  
INNER JOIN ContactGroupMember ON ContactGroupMember.ContactGroupId = ContactGroup.ContactGroupId
INNER JOIN Contact ON Contact.ContactId = ContactGroupMember.ContactId
WHERE ContactGroup.ContactGroupId = :groupId 
ORDER BY Contact.contactid

Questa è una bel metodo che visualizza tanti aspetti importanti per l'utlizzo di una query. Uno su tutti il caricamento dentro l'entità restituita dei dati. Questo però non permette di eseguire a livello JPQL dei filtri, ma serve una join tra tabelle per fare questo. Dopo i dati vengono caricati.
Come si vede la query si compone fortemente in base ai dati passati come parametri. Facendo o meno una join o un like, quindi eseguendo una query leggere, media o pesante. L'utilizzo del costrutto like aumenta sensibilmente il tempo di retrive dal db.
public List<Message> findMessages(Long userId, Date dateFrom, Date dateTo, String messageContent, int skipTo, int pageSize,
        String sortColumn, boolean ascending, String msisdn, EnMsisdnSearch msisdnSearch)
    {
        List<Message> messages;
        String content;
 
        LogUtil.log("finding messages", Level.INFO, null);
 
        if (dateTo != null)
        {
            Calendar cal;
            cal = Calendar.getInstance();
            cal.setTime(dateTo);
            cal.add(Calendar.DAY_OF_YEAR, 1);
            dateTo = cal.getTime();
        }
 
        if ((messageContent != null) && (messageContent.length() > 0))
            content = String.format("%%%s%%", messageContent);
        else
            content = null;
 
        String msisdnSearchQuery="";
        if(msisdn!=null)
        {
            switch (msisdnSearch)
            {
                    case EXACTMATCH:
                    msisdnSearchQuery="rcps.msisdn = :recipient AND ";
                    break;
                    case LIKEMATCH:
                    msisdnSearchQuery="rcps.msisdn LIKE :recipient AND ";
                    msisdn="%"+msisdn+"%";
                    break;
                    case ENDSBYMATCH:
                    msisdnSearchQuery="rcps.msisdn LIKE :recipient AND ";
                    msisdn="%"+msisdn;
                    break;
                    case STARTBYMATCH:
                    msisdnSearchQuery="rcps.msisdn LIKE :recipient AND ";
                    msisdn=msisdn+"%";
                    break;
                    default:
                    break;
            }
        }
 
        try
        {
            final String queryString;
            Query query;
 
            /*
             * 
             * */
 
            queryString = "" +
                "SELECT msg " +
                "FROM Message msg " +
                (msisdn!=null ? "INNER JOIN msg.messageRecipients rcps " : "")+
                "LEFT JOIN FETCH msg.messageRecipients " +
                "LEFT JOIN FETCH msg.messageContacts " +
                "LEFT JOIN FETCH msg.messageContactGroups " +
                "LEFT JOIN FETCH msg.messageRawRecipients " +
 
                "WHERE " +
                (dateFrom != null ? "(msg.enqueuedOn >= :dateFrom OR msg.processedOn >= :dateFrom OR msg.completedOn >= :dateFrom) AND " : "") +
                (dateTo != null ? "(msg.enqueuedOn <= :dateTo OR msg.processedOn <= :dateTo OR msg.completedOn <= :dateTo) AND " : "") +
                (content != null ? "(msg.message LIKE :messageContent) AND " : "") +
                msisdnSearchQuery +
                "msg.user.userId = :userId " +
                (sortColumn.length() > 0 ? String.format("ORDER BY %s %s", sortColumn, ascending ? "ASC" : "DESC") : "");
 
            query = entityManager.createQuery(queryString);
            query.setParameter("userId", userId);
            if (dateFrom != null)
                query.setParameter("dateFrom", dateFrom);
            if (dateTo != null)
                query.setParameter("dateTo", dateTo);
            if (content != null)
                query.setParameter("messageContent", content);
            if (msisdn != null )
                    query.setParameter("recipient", msisdn);
 
            query.setFirstResult(skipTo);
            query.setMaxResults(pageSize);
            messages = query.getResultList();
        }
        catch(RuntimeException ex)
        {
            LogUtil.log("find messages", Level.SEVERE, ex);
            throw ex;
        }
 
        return messages;
    }
Salvo diversa indicazione, il contenuto di questa pagina è sotto licenza Creative Commons Attribution-ShareAlike 3.0 License