JPA getSingleResult () oder null


136

Ich habe eine insertOrUpdateMethode, die ein einfügt, Entitywenn es nicht existiert, oder es aktualisiert, wenn es existiert. Um dies zu aktivieren, muss ich findByIdAndForeignKey, wenn es zurückgegeben hat, nulleinfügen, wenn nicht, dann aktualisieren. Das Problem ist, wie überprüfe ich, ob es existiert? Also habe ich es versucht getSingleResult. Aber es löst eine Ausnahme aus, wenn die

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;
}

aber getSingleResultwirft ein Exception.

Vielen Dank

Antworten:


266

Das Auslösen einer Ausnahme getSingleResult()zeigt an, dass sie nicht gefunden werden kann. Persönlich kann ich diese Art von API nicht ausstehen. Es erzwingt eine falsche Ausnahmebehandlung ohne wirklichen Nutzen. Sie müssen den Code nur in einen Try-Catch-Block einschließen.

Alternativ können Sie eine Liste abfragen und prüfen, ob sie leer ist. Das wirft keine Ausnahme. Da Sie technisch gesehen keine Primärschlüssel-Suche durchführen, kann es zu mehreren Ergebnissen kommen (selbst wenn einer, beide oder die Kombination Ihrer Fremdschlüssel oder Einschränkungen dies in der Praxis unmöglich macht). Dies ist wahrscheinlich die geeignetere Lösung.


115
Ich bin nicht einverstanden, getSingleResult()wird in Situationen wie: " Ich bin absolut sicher, dass dieser Datensatz existiert. Erschieß mich, wenn es nicht existiert " verwendet. Ich möchte nicht nulljedes Mal testen, wenn ich diese Methode verwende, da ich sicher bin , dass sie nicht zurückgegeben wird. Andernfalls verursacht es viel Boilerplate und defensive Programmierung. Und wenn der Datensatz wirklich nicht existiert (im Gegensatz zu dem, was wir angenommen haben), ist es viel besser, ihn zu haben NoResultException, als NullPointerExceptioneinige Zeilen später. Natürlich getSingleResult()wäre es fantastisch, zwei Versionen von zu haben, aber wenn ich eine abholen
müsste

8
@cletus Null ist in der Tat ein gültiger Rückgabewert für eine Datenbank.
Bill Rosmus

12
@TomaszNurkiewicz das ist ein guter Punkt. Es scheint jedoch, dass es eine Art "getSingleResultOrNull" geben sollte. Ich denke, Sie könnten einen Wrapper für solche erstellen.
cbmeeks

2
Hier einige Informationen zum Vorteil der Ausnahme, die von getSingleResult () ausgelöst werden: Abfragen können verwendet werden, um fast alles abzurufen, einschließlich des Werts einer einzelnen Spalte in einer einzelnen Zeile. Wenn getSingleResult () null zurückgeben würde, könnten Sie nicht feststellen, ob die Abfrage keiner Zeile entspricht oder ob die Abfrage einer Zeile entspricht, die ausgewählte Spalte jedoch null als Wert enthält. von: stackoverflow.com/a/12155901/1242321
user1242321

5
Es sollte Optional <T> zurückgeben. Dies ist ein guter Weg, um fehlende Werte anzuzeigen.
Vivek Kothari

33

Ich habe die Logik in der folgenden Hilfsmethode gekapselt.

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
Beachten Sie, dass Sie durch Aufrufen von Query.setMaxResults (1) etwas optimaler sein können. Da Query statusbehaftet ist, möchten Sie leider den Wert von Query.getMaxResults () erfassen und das Objekt in einem Try-finally-Block reparieren. Wenn Query.getFirstResult () etwas Interessantes zurückgibt, kann dies möglicherweise insgesamt fehlschlagen.
Patrick Linskey

So haben wir es in unserem Projekt umgesetzt. Hatte nie Probleme mit dieser Implementierung
Walv

25

Versuchen Sie dies in Java 8:

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

3
Sie können die Optionale durch Hinzufügen von.orElse(null)
Justin Rowe

24

Hier ist eine gute Option dafür:

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
Ordentlich! Ich würde allerdings akzeptieren TypedQuery<T>, in welchem ​​Fall das getResultList()dann schon richtig als List<T>.
Rup

In Kombination mit fetch()der Entität ist sie möglicherweise nicht vollständig ausgefüllt. Siehe stackoverflow.com/a/39235828/661414
Leukipp

1
Dies ist ein sehr schöner Ansatz. Beachten Sie, dass setMaxResults()es eine fließende Oberfläche hat, damit Sie schreiben können query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Dies sollte das effizienteste Aufrufschema in Java 8+ sein.
Dirk Hillbrecht

17

Spring hat hierfür eine Utility-Methode :

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

14

Ich habe getan (in Java 8):

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

Was meinst du mit Abfrage?
Enrico Giurin

Du meinst HibernateQuery? Was ist, wenn ich die reine JPA-API verwenden möchte? Es gibt keine solche Methode in javax.persistence.Query
Enrico Giurin

2
@EnricoGiurin, ich habe das Snippet bearbeitet. Funktioniert gut. Kein Try-Catch und keine list.size-Prüfung. Schönste Einzeilerlösung.
LovaBill

10

Ab JPA 2.2 können Sie anstelle von .getResultList()und überprüfen, ob die Liste leer ist oder einen Stream erstellen, den Stream zurückgeben und das erste Element übernehmen.

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

7

Wenn Sie den Try / Catch-Mechanismus verwenden möchten, um dieses Problem zu lösen, können Sie sich wie if / else verhalten. Ich habe try / catch verwendet, um einen neuen Datensatz hinzuzufügen, wenn ich keinen vorhandenen gefunden habe.

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

Hier ist eine typisierte / generische Version, die auf der Implementierung von Rodrigo IronMan basiert:

 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

Es gibt eine Alternative, die ich empfehlen würde:

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

Dies schützt vor einer Nullzeiger-Ausnahme und garantiert, dass nur 1 Ergebnis zurückgegeben wird.


4

Also tu das nicht!

Sie haben zwei Möglichkeiten:

  1. Führen Sie eine Auswahl aus, um den COUNT Ihrer Ergebnismenge zu erhalten, und ziehen Sie die Daten nur ein, wenn diese Anzahl ungleich Null ist. oder

  2. Verwenden Sie die andere Art von Abfrage (die eine Ergebnismenge erhält) und prüfen Sie, ob 0 oder mehr Ergebnisse vorliegen. Es sollte 1 haben, also ziehen Sie das aus Ihrer Ergebnissammlung und Sie sind fertig.

Ich würde dem zweiten Vorschlag in Übereinstimmung mit Cletus folgen. Es bietet eine bessere Leistung als (möglicherweise) 2 Abfragen. Auch weniger Arbeit.


1
Option 3 Versuchen / fangen Sie NoResultException
Ced

3

Durch Kombinieren der nützlichen Bits der vorhandenen Antworten (Begrenzen der Anzahl der Ergebnisse, Überprüfen, ob das Ergebnis eindeutig ist) und Verwenden des Namens der stabilisierbaren Methode (Ruhezustand) erhalten wir:

/**
 * 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

Die undokumentierte Methode uniqueResultOptionalin org.hibernate.query.Query sollte den Trick ausführen. Anstatt einen zu fangen NoResultException, können Sie einfach anrufen query.uniqueResultOptional().orElse(null).


2

Ich habe dies gelöst, indem ich verwendet List<?> myList = query.getResultList();und überprüft habe, ob es myList.size()gleich Null ist.


1

Hier ist die gleiche Logik wie von anderen vorgeschlagen (Abrufen der Ergebnisliste, Zurückgeben des einzigen Elements oder Null) unter Verwendung von Google Guava und einer TypedQuery.

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

Beachten Sie, dass Guava die nicht intuitive IllegalArgumentException zurückgibt, wenn die Ergebnismenge mehr als ein Ergebnis enthält. (Die Ausnahme ist für Clients von getOnlyElement () sinnvoll, da sie die Ergebnisliste als Argument verwendet, für Clients von getSingleResultOrNull () jedoch weniger verständlich ist.)


1

Hier ist eine weitere Erweiterung, diesmal in Scala.

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

Mit diesem Zuhälter:

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

Schauen Sie sich diesen Code an:

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

Wenn findFirst()aufgerufen wird, kann möglicherweise eine NullPointerException ausgelöst werden.

Der beste Ansatz ist:

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


0

Die gesamte Lösung "Versuchen Sie ausnahmslos, neu zu schreiben" auf dieser Seite weist daher ein kleines Problem auf. Entweder wird keine NonUnique-Ausnahme ausgelöst, oder es wird auch in einigen falschen Fällen eine Ausnahme ausgelöst (siehe unten).

Ich denke, die richtige Lösung ist (vielleicht) folgende:

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;
}

Es wird mit null zurückgegeben, wenn die Liste 0 Elemente enthält, und es wird nicht eindeutig zurückgegeben, wenn die Liste verschiedene Elemente enthält. Es wird jedoch nicht eindeutig zurückgegeben, wenn eines Ihrer ausgewählten Elemente nicht ordnungsgemäß entworfen wurde und dasselbe Objekt mehrmals zurückgibt.

Fühlen Sie sich frei zu kommentieren.


0

Ich habe dies erreicht, indem ich eine Ergebnisliste erhalten und dann überprüft habe, ob sie leer ist

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

Es ist so nervig, dass es getSingleResult()Ausnahmen gibt

Würfe:

  1. NoResultException - wenn kein Ergebnis vorliegt
  2. NonUniqueResultException - Wenn mehr als ein Ergebnis und eine andere Ausnahme vorliegen, zu der Sie weitere Informationen aus der Dokumentation erhalten können

-3

Das funktioniert bei mir:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.