JPA getSingleResult () lub null


136

Mam insertOrUpdatemetodę, która wstawia, Entitygdy nie istnieje, lub aktualizuje ją, jeśli tak. Aby to włączyć, muszę findByIdAndForeignKey, jeśli zwróciło, nullwstaw, jeśli nie, to zaktualizuj. Problem w tym, jak sprawdzić, czy istnieje? Więc spróbowałem getSingleResult. Ale zgłasza wyjątek, jeśli

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

ale getSingleResultrzuca Exception.

Dzięki

Odpowiedzi:


266

Zgłoszenie wyjątku getSingleResult()oznacza, że ​​nie można go znaleźć. Osobiście nie mogę znieść tego rodzaju API. Wymusza fałszywą obsługę wyjątków bez realnej korzyści. Wystarczy zawinąć kod w blok try-catch.

Alternatywnie możesz zapytać o listę i sprawdzić, czy jest pusta. To nie stanowi wyjątku. Właściwie, ponieważ technicznie nie wykonujesz wyszukiwania klucza podstawowego, może wystąpić wiele wyników (nawet jeśli jeden, oba lub kombinacja kluczy obcych lub ograniczeń uniemożliwia to w praktyce), więc jest to prawdopodobnie bardziej odpowiednie rozwiązanie.


115
Nie zgadzam się, getSingleResult()jest używane w sytuacjach typu: „ Jestem całkowicie pewien, że ta płyta istnieje. Zastrzel mnie, jeśli jej nie ma ”. Nie chcę testować za nullkażdym razem, gdy używam tej metody, ponieważ jestem pewien, że jej nie zwróci. W przeciwnym razie powoduje to wiele schematycznych i defensywnych programów. A jeśli rekord naprawdę nie istnieje (w przeciwieństwie do tego, co założyliśmy), znacznie lepiej jest go NoResultExceptionporównać z NullPointerExceptionkilkoma wierszami później. Oczywiście posiadanie dwóch wersji getSingleResult()byłoby super, ale gdybym miał odebrać jedną ...
Tomasz Nurkiewicz

8
@cletus Null jest rzeczywiście prawidłową wartością zwracaną dla bazy danych.
Bill Rosmus

12
@TomaszNurkiewicz to dobra uwaga. Jednak wygląda na to, że powinien istnieć jakiś rodzaj „getSingleResultOrNull”. Myślę, że możesz stworzyć opakowanie dla takich.
cbmeeks

2
Oto kilka informacji na temat korzyści z rozpoczęcia wyjątku rzucanego przez getSingleResult (): Zapytania mogą służyć do pobierania prawie wszystkiego, w tym wartości pojedynczej kolumny w jednym wierszu. Jeśli getSingleResult () zwróci wartość null, nie można stwierdzić, czy zapytanie nie pasuje do żadnego wiersza lub czy zapytanie pasuje do wiersza, ale wybrana kolumna zawiera wartość null. od: stackoverflow.com/a/12155901/1242321
user1242321

5
Powinien zwrócić Optional <T>. To dobry sposób na wskazanie brakujących wartości.
Vivek Kothari

33

Logikę zawarłem w poniższej metodzie pomocniczej.

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
Zauważ, że możesz być nieco bardziej optymalny, wywołując Query.setMaxResults (1). Niestety, ponieważ Query jest stanowe, będziesz chciał przechwycić wartość Query.getMaxResults () i naprawić obiekt w bloku try-final, a może po prostu całkowicie się nie powiedzie, jeśli Query.getFirstResult () zwróci coś interesującego.
Patrick Linskey

tak to wdrożyliśmy w naszym projekcie. Nigdy nie miałem żadnych problemów z tą implementacją
walv

25

Spróbuj tego w Javie 8:

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

3
Możesz pozbyć się Opcjonalnego, dodając.orElse(null)
Justin Rowe

24

Oto dobra opcja, aby to zrobić:

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
Schludny! Zgodziłbym się TypedQuery<T>jednak, w takim przypadku getResultList()jest już poprawnie wpisany jako List<T>.
Rup

W połączeniu z fetch()jednostką może nie zostać wypełniona w całości. Zobacz stackoverflow.com/a/39235828/661414
Leukipp,

1
To bardzo fajne podejście. Pamiętaj, że setMaxResults()ma płynny interfejs, dzięki czemu możesz pisać query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Powinien to być najbardziej efektywny schemat połączeń w Javie 8+.
Dirk Hillbrecht

17

Spring ma do tego użyteczną metodę :

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

15

Zrobiłem (w Javie 8):

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

co masz na myśli przez zapytanie?
Enrico Giurin

Masz na myśli HibernateQuery? A jeśli chcę używać czystego API JPA? Nie ma takiej metody w javax.persistence.Query
Enrico Giurin

2
@EnricoGiurin, dokonałem edycji fragmentu. Dobrze pracować. Bez próbnego łapania i sprawdzania rozmiaru listy. Najładniejsze rozwiązanie z jednym wkładem.
LovaBill

10

Z JPA 2.2 zamiast .getResultList()sprawdzania, czy lista jest pusta lub tworzenia strumienia, możesz zwrócić strumień i pobrać pierwszy element.

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

7

Jeśli chcesz użyć mechanizmu try / catch do rozwiązania tego problemu, możesz go użyć do działania jak if / else. Użyłem try / catch, aby dodać nowy rekord, gdy nie znalazłem istniejącego.

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

Oto wersja typowana / generyczna oparta na implementacji Rodrigo IronMana:

 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

Jest alternatywa, którą bym polecił:

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

Zabezpiecza to przed wyjątkiem zerowego wskaźnika, gwarantuje, że zostanie zwrócony tylko 1 wynik.


4

Więc nie rób tego!

Masz dwie możliwości:

  1. Uruchom selekcję, aby uzyskać LICZBĘ swojego zestawu wyników i pobieraj dane tylko wtedy, gdy ta liczba jest różna od zera; lub

  2. Użyj innego rodzaju zapytania (otrzymującego zestaw wyników) i sprawdź, czy ma 0 lub więcej wyników. Powinien mieć 1, więc wyciągnij go z kolekcji wyników i gotowe.

Pójdę z drugą sugestią, w porozumieniu z Cletusem. Daje lepszą wydajność niż (potencjalnie) 2 zapytania. Mniej pracy.


1
Wariant 3 try / połowu NoResultException
Ced

3

Łącząc przydatne bity istniejących odpowiedzi (ograniczając liczbę wyników, sprawdzając, czy wynik jest unikalny) i używając ustalonej nazwy metody (Hibernate), otrzymujemy:

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

Nieudokumentowana metoda uniqueResultOptionalw org.hibernate.query.Query powinna załatwić sprawę. Zamiast łapać NoResultException, możesz po prostu zadzwonić query.uniqueResultOptional().orElse(null).



1

Oto ta sama logika, co sugerowali inni (pobierz resultList, zwróć jej jedyny element lub null), używając Google Guava i TypedQuery.

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

Zwróć uwagę, że Guava zwróci nieintuicyjny wyjątek IllegalArgumentException, jeśli zestaw wyników zawiera więcej niż jeden wynik. (Wyjątek ma sens dla klientów metody getOnlyElement (), ponieważ przyjmuje listę wyników jako argument, ale jest mniej zrozumiały dla klientów metody getSingleResultOrNull ().)


1

Oto kolejne rozszerzenie, tym razem w Scali.

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

Z tym alfonsem:

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

Spójrz na ten kod:

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

Kiedy findFirst() jest wywoływana, może zostać wyrzucony wyjątek NullPointerException.

najlepszym podejściem jest:

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


0

Tak więc wszystkie rozwiązania „spróbuj przepisać bez wyjątku” na tej stronie mają drobny problem. Albo nie zgłasza wyjątku NonUnique, albo też w niektórych niewłaściwych przypadkach (patrz poniżej).

Myślę, że właściwym rozwiązaniem jest (może) to:

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

Zwraca wartość null, jeśli na liście znajduje się element 0, zwraca wartość nieunikalną, jeśli na liście są różne elementy, ale nie zwraca wartości nieunikalnej, gdy jeden z wybranych elementów nie jest poprawnie zaprojektowany i zwraca ten sam obiekt więcej niż jeden razy.

Zapraszam do komentowania.


0

Osiągnąłem to, uzyskując listę wyników, a następnie sprawdzając, czy jest pusta

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

To jest tak irytujące, że getSingleResult()rzuca wyjątki

Rzuty:

  1. NoResultException - jeśli nie ma wyniku
  2. NonUniqueResultException - jeśli więcej niż jeden wynik i jakiś inny wyjątek, o którym możesz uzyskać więcej informacji z ich dokumentacji

-3

To mi pasuje:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.