JPA getSingleResult () o null


136

Ho un insertOrUpdatemetodo che inserisce un Entityquando non esiste o lo aggiorna se lo fa. Per abilitare questo, devo findByIdAndForeignKey, se restituito nullinserire se non quindi aggiornare. Il problema è come posso verificare se esiste? Quindi ci ho provato getSingleResult. Ma genera un'eccezione se il

public Profile findByUserNameAndPropertyName(String userName, String propertyName) {
    String namedQuery = Profile.class.getSimpleName() + ".findByUserNameAndPropertyName";
    Query query = entityManager.createNamedQuery(namedQuery);
    query.setParameter("name", userName);
    query.setParameter("propName", propertyName);
    Object result = query.getSingleResult();
    if (result == null) return null;
    return (Profile) result;
}

ma getSingleResultgenera un Exception.

Grazie

Risposte:


266

Lanciare un'eccezione è come getSingleResult() indica che non può essere trovato. Personalmente non sopporto questo tipo di API. Forza la gestione spuria delle eccezioni senza alcun vantaggio reale. Devi solo racchiudere il codice in un blocco try-catch.

In alternativa puoi cercare un elenco e vedere se è vuoto. Questo non fa eccezione. In realtà, dal momento che tecnicamente non stai effettuando una ricerca della chiave primaria, potrebbero esserci più risultati (anche se uno, entrambi o la combinazione di chiavi esterne o vincoli lo rendono impossibile nella pratica), quindi questa è probabilmente la soluzione più appropriata.


115
Non sono d'accordo, getSingleResult()viene utilizzato in situazioni come: " Sono assolutamente sicuro che questo disco esista. Sparami se non lo fa ". Non voglio testare nullogni volta che utilizzo questo metodo perché sono sicuro che non lo restituirà. Altrimenti provoca molta programmazione di caldaia e programmazione difensiva. E se il disco non esiste davvero (contrariamente a quanto ipotizzato), è molto meglio NoResultExceptionrispetto a NullPointerExceptionpoche righe successive. Naturalmente avere due versioni di getSingleResult()sarebbe fantastico, ma se dovessi prenderne uno ...
Tomasz Nurkiewicz,

8
@cletus Null è effettivamente un valore di ritorno valido per un database.
Bill Rosmus,

12
@TomaszNurkiewicz è un buon punto. Tuttavia, sembra che ci dovrebbe essere un tipo di "getSingleResultOrNull". Immagino che potresti creare un wrapper per questo.
cbmeeks,

2
Ecco alcune informazioni in termini di vantaggio dell'eccezione iniziata da getSingleResult (): Le query possono essere utilizzate per recuperare quasi tutto, incluso il valore di una singola colonna in una singola riga. Se getSingleResult () restituisce null, non è possibile stabilire se la query non corrisponde a nessuna riga o se la query corrisponde a una riga ma la colonna selezionata contiene null come valore. da: stackoverflow.com/a/12155901/1242321
user1242321

5
Dovrebbe restituire <T> opzionale. Questo è un buon modo per indicare i valori mancanti.
Vivek Kothari,

33

Ho incapsulato la logica nel seguente metodo di supporto.

public class JpaResultHelper {
    public static Object getSingleResultOrNull(Query query){
        List results = query.getResultList();
        if (results.isEmpty()) return null;
        else if (results.size() == 1) return results.get(0);
        throw new NonUniqueResultException();
    }
}

2
Nota che puoi essere un po 'più ottimale chiamando Query.setMaxResults (1). Sfortunatamente, poiché Query è stateful, vorrai catturare il valore di Query.getMaxResults () e sistemare l'oggetto in un blocco try-finally, e forse fallire del tutto se Query.getFirstResult () restituisce qualcosa di interessante.
Patrick Linskey,

è così che l'abbiamo implementato nel nostro progetto. Non
ho

25

Prova questo in Java 8:

Optional first = query.getResultList().stream().findFirst();

3
Puoi sbarazzarti dell'Opzionale aggiungendo.orElse(null)
Justin Rowe il

24

Ecco una buona opzione per farlo:

public static <T> T getSingleResult(TypedQuery<T> query) {
    query.setMaxResults(1);
    List<T> list = query.getResultList();
    if (list == null || list.isEmpty()) {
        return null;
    }

    return list.get(0);
}

2
! Neat Accetterei TypedQuery<T>però, nel qual caso getResultList()quindi è già correttamente digitato come a List<T>.
Rup,

In combinazione con fetch()l'entità potrebbe non essere completamente popolata. Vedere stackoverflow.com/a/39235828/661414
Leukipp

1
Questo è un approccio molto carino. Nota che setMaxResults()ha un'interfaccia fluida in modo da poter scrivere query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Questo dovrebbe essere lo schema di chiamate più efficiente in Java 8+.
Dirk Hillbrecht,

17

Spring ha un metodo di utilità per questo:

TypedQuery<Profile> query = em.createNamedQuery(namedQuery, Profile.class);
...
return org.springframework.dao.support.DataAccessUtils.singleResult(query.getResultList());

15

Ho fatto (in Java 8):

query.getResultList().stream().findFirst().orElse(null);

cosa intendi per query?
Enrico Giurin,

Intendi HibernateQuery? Cosa succede se desidero utilizzare l'API JPA puro? Non esiste un metodo simile in javax.persistence.Query
Enrico Giurin,

2
@EnricoGiurin, ho modificato lo snippet. Funziona bene. Nessun try-catch e nessun controllo list.size. La migliore soluzione di rivestimento.
LovaBill,

10

Da JPA 2.2 , invece di .getResultList()e controllare se l'elenco è vuoto o creare un flusso è possibile restituire il flusso e prendere il primo elemento.

.getResultStream()
.findFirst()
.orElse(null);

7

Se si desidera utilizzare il meccanismo try / catch per gestire questo problema ... allora può essere usato per agire come se / else. Ho usato il tentativo / cattura per aggiungere un nuovo record quando non ne ho trovato uno esistente.

try {  //if part

    record = query.getSingleResult();   
    //use the record from the fetched result.
}
catch(NoResultException e){ //else part
    //create a new record.
    record = new Record();
    //.........
    entityManager.persist(record); 
}

6

Ecco una versione tipizzata / generica, basata sull'implementazione di Rodrigo IronMan:

 public static <T> T getSingleResultOrNull(TypedQuery<T> query) {
    query.setMaxResults(1);
    List<T> list = query.getResultList();
    if (list.isEmpty()) {
        return null;
    }
    return list.get(0);
}

5

C'è un'alternativa che consiglierei:

Query query = em.createQuery("your query");
List<Element> elementList = query.getResultList();
return CollectionUtils.isEmpty(elementList ) ? null : elementList.get(0);

Questa protezione contro l'eccezione del puntatore null, garantisce solo 1 risultato restituito.


4

Quindi non farlo!

Hai due opzioni:

  1. Esegui una selezione per ottenere il COUNT del set di risultati e inserisci i dati solo se questo conteggio è diverso da zero; o

  2. Utilizzare l'altro tipo di query (che ottiene un set di risultati) e verificare se ha 0 o più risultati. Dovrebbe avere 1, quindi estrailo dalla raccolta dei risultati e il gioco è fatto.

Andrei con il secondo suggerimento, in accordo con Cletus. Offre prestazioni migliori rispetto (potenzialmente) a 2 query. Anche meno lavoro.


1
Opzione 3 Prova a catturare NoResultException
Ced

3

Combinando i bit utili delle risposte esistenti (limitando il numero di risultati, verificando che il risultato sia univoco) e usando il nome del metodo stabilito (Hibernate), otteniamo:

/**
 * Return a single instance that matches the query, or null if the query returns no results.
 *
 * @param query query (required)
 * @param <T> result record type
 * @return record or null
 */
public static <T> T uniqueResult(@NotNull TypedQuery<T> query) {
    List<T> results = query.setMaxResults(2).getResultList();
    if (results.size() > 1) throw new NonUniqueResultException();
    return results.isEmpty() ? null : results.get(0);
}

3

Il metodo non documentato uniqueResultOptionalin org.hibernate.query.Query dovrebbe fare il trucco. Invece di dover prendere un NoResultExceptionpuoi semplicemente chiamare query.uniqueResultOptional().orElse(null).



1

Ecco la stessa logica suggerita da altri (ottieni l'elenco dei risultati, restituisce il suo unico elemento o null), utilizzando Google Guava e un TypedQuery.

public static <T> getSingleResultOrNull(final TypedQuery<T> query) {
    return Iterables.getOnlyElement(query.getResultList(), null); 
}

Si noti che Guava restituirà illegalintuitivo IllegalArgumentException se il set di risultati ha più di un risultato. (L'eccezione ha senso per i client di getOnlyElement (), poiché considera l'elenco dei risultati come argomento, ma è meno comprensibile per i client di getSingleResultOrNull ().)


1

Ecco un'altra estensione, questa volta a Scala.

customerQuery.getSingleOrNone match {
  case Some(c) => // ...
  case None    => // ...
}

Con questo magnaccia:

import javax.persistence.{NonUniqueResultException, TypedQuery}
import scala.collection.JavaConversions._

object Implicits {

  class RichTypedQuery[T](q: TypedQuery[T]) {

    def getSingleOrNone : Option[T] = {

      val results = q.setMaxResults(2).getResultList

      if (results.isEmpty)
        None
      else if (results.size == 1)
        Some(results.head)
      else
        throw new NonUniqueResultException()
    }
  }

  implicit def query2RichQuery[T](q: TypedQuery[T]) = new RichTypedQuery[T](q)
}

1

Guarda questo codice:

return query.getResultList().stream().findFirst().orElse(null);

quando findFirst() viene chiamato, forse può essere lanciata una NullPointerException.

il miglior approccio è:

return query.getResultList().stream().filter(Objects::nonNull).findFirst().orElse(null);


0

Quindi tutta la soluzione "prova a riscrivere senza eccezioni" in questa pagina presenta un problema minore. O non genera l'eccezione NonUnique, né in alcuni casi sbagliati (vedi sotto).

Penso che la soluzione corretta sia (forse) questa:

public static <L> L getSingleResultOrNull(TypedQuery<L> query) {
    List<L> results = query.getResultList();
    L foundEntity = null;
    if(!results.isEmpty()) {
        foundEntity = results.get(0);
    }
    if(results.size() > 1) {
        for(L result : results) {
            if(result != foundEntity) {
                throw new NonUniqueResultException();
            }
        }
    }
    return foundEntity;
}

Viene restituito con null se è presente 0 elemento nell'elenco, restituisce non univoco se ci sono elementi diversi nell'elenco, ma non restituisce non univoco quando una delle selezioni non è progettata correttamente e restituisce lo stesso oggetto più di una volta.

Sentiti libero di commentare.


0

Ho raggiunto questo obiettivo ottenendo un elenco di risultati e verificando se è vuoto

public boolean exist(String value) {
        List<Object> options = getEntityManager().createNamedQuery("AppUsers.findByEmail").setParameter('email', value).getResultList();
        return !options.isEmpty();
    }

È così fastidioso che getSingleResult()genera eccezioni

Produce:

  1. NoResultException - se non ci sono risultati
  2. NonUniqueResultException - se più di un risultato e qualche altra eccezione è possibile ottenere maggiori informazioni dalla loro documentazione

-3

Questo per me funziona:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.