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

Eugene Ramirez
źródło

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.

cletus
źródło
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();
    }
}
Eugene Katz
źródło
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();
Impala67
źródło
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);
}
Rodrigo IronMan
źródło
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());
heenenee
źródło
15

Zrobiłem (w Javie 8):

query.getResultList().stream().findFirst().orElse(null);
Zhurov Konstantin
źródło
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);
Serafiny
źródło
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); 
}
Sorter
źródło
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);
}
Emmanuel Touzery
źródło
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.

asy.
źródło
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.

Carl Smotricz
źródło
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);
}
Peter Walser
źródło
3

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

at_sof
źródło
2

Rozwiązałem to, używając List<?> myList = query.getResultList();i sprawdzając, czy myList.size()jest równe zero.

Сергій Катрюк
źródło
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 ().)

tpdi
źródło
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)
}
Pete Montgomery
źródło
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);

Leandro Ferreira
źródło
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.

tg44
źródło
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
Uchephilz
źródło
-3

To mi pasuje:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
peterzinho16
źródło