Utilizzo dei parametri di query JPA

1. Introduzione

La creazione di query utilizzando JPA non è difficile; tuttavia, a volte dimentichiamo cose semplici che fanno un'enorme differenza.

Una di queste cose sono i parametri di query JPA, e questo è ciò di cui parleremo.

2. Cosa sono i parametri della query?

Cominciamo spiegando cosa sono i parametri di query.

I parametri di query sono un modo per creare ed eseguire query con parametri. Quindi, invece di:

SELECT * FROM employees e WHERE e.emp_number = '123';

Faremmo:

SELECT * FROM employees e WHERE e.emp_number = ?;

Utilizzando un'istruzione preparata JDBC, è necessario impostare il parametro prima di eseguire la query:

pStatement.setString(1, 123);

3. Perché dovremmo utilizzare i parametri di query?

Invece di utilizzare i parametri di query avremmo potuto scegliere di utilizzare i letterali, tuttavia, non è il modo consigliato per farlo, come vedremo ora.

Riscriviamo la query precedente per ottenere dipendenti da emp_number utilizzando l'API JPA, ma invece di utilizzare un parametro utilizzeremo un valore letterale in modo da poter illustrare chiaramente la situazione:

String empNumber = "A123"; TypedQuery query = em.createQuery( "SELECT e FROM Employee e WHERE e.empNumber = '" + empNumber + "'", Employee.class); Employee employee = query.getSingleResult();

Questo approccio presenta alcuni inconvenienti:

  • I parametri di incorporamento introducono un rischio per la sicurezza rendendoci vulnerabili agli attacchi di iniezione JPQL. Invece del valore atteso, un utente malintenzionato può iniettare qualsiasi espressione JPQL inaspettata e potenzialmente pericolosa
  • A seconda dell'implementazione JPA che utilizziamo e dell'euristica della nostra applicazione, la cache delle query potrebbe esaurirsi. Una nuova query può essere costruita, compilata e memorizzata nella cache ogni volta che la usiamo con ogni nuovo valore / parametro. Come minimo, non sarà efficiente e potrebbe anche portare a un OutOfMemoryError inaspettato

4. Parametri di query JPA

Simile ai parametri dell'istruzione preparata da JDBC, JPA specifica due modi diversi per scrivere query con parametri utilizzando:

  • Parametri posizionali
  • Parametri denominati

Possiamo usare parametri posizionali o con nome ma non dobbiamo mescolarli all'interno della stessa query.

4.1. Parametri posizionali

L'uso dei parametri posizionali è un modo per evitare i problemi sopra menzionati elencati in precedenza.

Vediamo come scriveremmo una simile query con l'aiuto di parametri posizionali:

TypedQuery query = em.createQuery( "SELECT e FROM Employee e WHERE e.empNumber = ?1", Employee.class); String empNumber = "A123"; Employee employee = query.setParameter(1, empNumber).getSingleResult();

Come abbiamo visto nell'esempio precedente, dichiariamo questi parametri all'interno della query digitando un punto interrogativo seguito da un numero intero positivo . Inizieremo con 1 e andremo avanti, incrementandolo di uno ogni volta.

Possiamo utilizzare lo stesso parametro più di una volta all'interno della stessa query, il che rende questi parametri più simili ai parametri denominati.

La numerazione dei parametri è una funzione molto utile poiché migliora l'usabilità, la leggibilità e la manutenzione.

Vale la pena ricordare che l' associazione di parametri posizionali è supportata anche da query SQL native .

4.2. Parametri posizionali con valori di raccolta

Come affermato in precedenza, possiamo anche utilizzare parametri con valori di raccolta:

TypedQuery query = entityManager.createQuery( "SELECT e FROM Employee e WHERE e.empNumber IN (?1)" , Employee.class); List empNumbers = Arrays.asList("A123", "A124"); List employees = query.setParameter(1, empNumbers).getResultList();

4.3. Parametri denominati

I parametri denominati sono abbastanza simili ai parametri posizionali; tuttavia, utilizzandoli, rendiamo i parametri più espliciti e la query diventa più leggibile:

TypedQuery query = em.createQuery( "SELECT e FROM Employee e WHERE e.empNumber = :number" , Employee.class); String empNumber = "A123"; Employee employee = query.setParameter("number", empNumber).getSingleResult();

La query di esempio precedente è la stessa della prima ma abbiamo utilizzato : number , un parametro denominato, invece di ? 1 .

Possiamo vedere che abbiamo dichiarato il parametro con i due punti seguiti da un identificatore di stringa (identificatore JPQL) che è un segnaposto per il valore effettivo che verrà impostato in fase di esecuzione. Prima di eseguire la query, il parametro oi parametri devono essere impostati emettendo il metodo setParameter .

Una cosa interessante notare è che il TypedQuery metodo supporta il concatenamento che diventa molto utile quando più parametri devono essere impostati.

Andiamo avanti e creiamo una variazione della query precedente utilizzando due parametri denominati per illustrare il concatenamento dei metodi:

TypedQuery query = em.createQuery( "SELECT e FROM Employee e WHERE e.name = :name AND e.age = :empAge" , Employee.class); String empName = "John Doe"; int empAge = 55; List employees = query .setParameter("name", empName) .setParameter("empAge", empAge) .getResultList();

Qui stiamo recuperando tutti i dipendenti con il nome e l'età indicati. Come si vede chiaramente e ci si può aspettare, possiamo creare query con più parametri e tutte le occorrenze richieste.

Se per qualche ragione abbiamo bisogno di usare lo stesso parametro molte volte all'interno della stessa query, dobbiamo solo impostarlo una volta immettendo il metodosetParameter ”. In fase di esecuzione, i valori specificati sostituiranno ogni occorrenza del parametro.

Infine, vale la pena ricordare che la specifica API Java Persistence non obbliga i parametri denominati a essere supportati dalle query native . Anche quando alcune implementazioni come Hibernate lo supportano, dobbiamo tenere in considerazione che se lo usiamo, la query non sarà così portabile.

4.4. Parametri denominati con valori di raccolta

Per chiarezza, dimostriamo anche come funziona con i parametri con valori di raccolta:

TypedQuery query = entityManager.createQuery( "SELECT e FROM Employee e WHERE e.empNumber IN (:numbers)" , Employee.class); List empNumbers = Arrays.asList("A123", "A124"); List employees = query.setParameter("numbers", empNumbers).getResultList();

Come possiamo vedere, funziona in modo simile ai parametri posizionali.

5. Parametri di query dei criteri

A JPA query may be built by using the JPA Criteria API, which Hibernate's official documentation explains in great detail.

In this type of query, we represent parameters by using objects instead of names or indices.

Let's build the same query again but this time using the Criteria API to demonstrate how to handle query parameters when dealing with CriteriaQuery:

CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cQuery = cb.createQuery(Employee.class); Root c = cQuery.from(Employee.class); ParameterExpression paramEmpNumber = cb.parameter(String.class); cQuery.select(c).where(cb.equal(c.get(Employee_.empNumber), paramEmpNumber)); TypedQuery query = em.createQuery(cQuery); String empNumber = "A123"; query.setParameter(paramEmpNumber, empNumber); Employee employee = query.getResultList();

For this type of query, the parameter's mechanic is a little bit different since we use a parameter object but in essence, there's no difference.

Within the previous example, we can see the usage of the Employee_ class. We generated this class with the Hibernate metamodel generator. These components are part of the static JPA metamodel, which allows criteria queries to be built in a strongly-typed manner.

6. Conclusione

In questo articolo, ci siamo concentrati sui meccanismi di creazione di query utilizzando parametri di query JPA o parametri di input.

Abbiamo appreso che abbiamo due tipi di parametri di query, posizionali e denominati. Sta a noi decidere quale si adatta meglio ai nostri obiettivi.

Vale anche la pena notare che tutti i parametri di query devono essere a valore singolo tranne nelle espressioni. Perché nelle espressioni, possiamo usare parametri di input con valori di raccolta, come array o List come mostrato negli esempi precedenti.

Il codice sorgente di questo tutorial, come al solito, è disponibile su GitHub.