JPA getSingleResult () или null


138

У меня есть insertOrUpdateметод, который вставляет, Entityкогда он не существует, или обновляет его, если он есть. Чтобы включить это, я должен findByIdAndForeignKey, если он вернул nullвставку, если нет, то обновить. Проблема в том, как проверить, существует ли он? Я попробовал getSingleResult. Но это вызывает исключение, если

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

но getSingleResultбросает Exception.

Благодарность

Ответы:


269

Выдача исключения - это то, как getSingleResult()указывает, что его нельзя найти. Лично я терпеть не могу такого рода API. Он вызывает ложную обработку исключений без какой-либо реальной пользы. Вам просто нужно поместить код в блок try-catch.

В качестве альтернативы вы можете запросить список и посмотреть, пуст ли он. Это не вызывает исключения. На самом деле, поскольку вы не выполняете поиск по первичному ключу технически, может быть несколько результатов (даже если один, оба или комбинация ваших внешних ключей или ограничений делают это невозможным на практике), поэтому это, вероятно, более подходящее решение.


118
Я не согласен, getSingleResult()используется в ситуациях типа: « Я полностью уверен, что эта запись существует. Стреляйте в меня, если нет ». Я не хочу nullкаждый раз тестировать этот метод, потому что уверен, что он его не вернет. В противном случае это вызовет много шаблонного и защитного программирования. И если запись действительно не существует (в NoResultExceptionотличие от того, что мы предполагали), гораздо лучше сравнить ее с NullPointerExceptionнесколькими строками позже. Конечно, getSingleResult()было бы здорово иметь две версии , но если мне нужно выбрать одну ...
Томаш Нуркевич

8
@cletus Null действительно является допустимым возвращаемым значением для базы данных.
Билл Росмус,

13
@TomaszNurkiewicz, это хороший аргумент. Однако, похоже, должен быть какой-то тип getSingleResultOrNull. Думаю, вы могли бы создать для этого обертку.
cbmeeks

3
Вот некоторая информация о преимуществах исключения, вызванного getSingleResult (): Запросы можно использовать для получения практически всего, включая значение одного столбца в одной строке. Если getSingleResult () вернет null, вы не сможете определить, соответствует ли запрос какой-либо строке или соответствует ли запрос строке, но выбранный столбец содержит значение NULL в качестве значения. из: stackoverflow.com/a/12155901/1242321
user1242321

5
Он должен вернуть Optional <T>. Это хороший способ указать отсутствующие значения.
Вивек Котари

34

Я инкапсулировал логику в следующем вспомогательном методе.

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
Обратите внимание, что вы можете быть немного более оптимальным, вызвав Query.setMaxResults (1). К сожалению, поскольку Query сохраняет состояние, вы захотите захватить значение Query.getMaxResults () и исправить объект в блоке try-finally, и, возможно, просто потерпеть неудачу, если Query.getFirstResult () вернет что-нибудь интересное.
Патрик Лински,

вот как мы реализовали это в нашем проекте. Никогда не было проблем с этой реализацией
walv

30

Попробуйте это в Java 8:

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

4
Вы можете избавиться от Необязательного, добавив.orElse(null)
Justin Rowe

24

Вот хороший вариант для этого:

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
Аккуратно! Я бы согласился TypedQuery<T>, и в этом случае getResultList()файл уже правильно набран как List<T>.
Rup

В сочетании с fetch()сущностью может быть заполнено не полностью. См stackoverflow.com/a/39235828/661414
Leukipp

1
Это очень хороший подход. Обратите внимание, что у setMaxResults()него свободный интерфейс, поэтому вы можете писать query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Это должна быть самая эффективная схема вызова в Java 8+.
Дирк Хиллбрехт


16

Я сделал (на Java 8):

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

что вы подразумеваете под запросом?
Enrico Giurin

Вы имеете в виду HibernateQuery? Что, если я хочу использовать чистый JPA api? В javax.persistence.Query нет такого метода
Энрико Джурин

2
@EnricoGiurin, я отредактировал отрывок. Работают нормально. Никаких попыток и проверки list.size. Лучшее решение с одним вкладышем.
LovaBill

10

Из JPA 2.2 вместо .getResultList()проверки того, пуст ли список или создания потока, вы можете вернуть поток и взять первый элемент.

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

7

Если вы хотите использовать механизм try / catch для решения этой проблемы ... тогда он может действовать как if / else. Я использовал команду try / catch, чтобы добавить новую запись, когда не нашел существующей.

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

Вот типизированная / обобщенная версия, основанная на реализации Родриго Айронмана:

 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

Есть альтернатива, которую я бы порекомендовал:

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

Это защищает от исключения Null Pointer Exception, гарантирует, что будет возвращен только 1 результат.


4

Так что не делай этого!

У вас есть два варианта:

  1. Выполните выборку, чтобы получить COUNT вашего набора результатов, и извлеките данные только в том случае, если это число не равно нулю; или

  2. Используйте другой тип запроса (который получает набор результатов) и проверьте, дает ли он 0 или более результатов. У него должно быть 1, поэтому вытащите его из своей коллекции результатов, и все готово.

Я бы согласился со вторым предложением, согласившись с Клетусом. Это дает лучшую производительность, чем (потенциально) 2 запроса. Также меньше работы.


1
Вариант 3 Попытка / отлов исключения NoResultException
Ced

3

Комбинируя полезные биты существующих ответов (ограничение количества результатов, проверка уникальности результата) и использование установленного имени метода (Hibernate), мы получаем:

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

Недокументированный метод uniqueResultOptionalв org.hibernate.query.Query должен помочь. Вместо того, чтобы поймать, NoResultExceptionвы можете просто позвонить query.uniqueResultOptional().orElse(null).



1

Здесь та же логика, что предлагали другие (получить список результатов, вернуть его единственный элемент или ноль), используя Google Guava и TypedQuery.

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

Обратите внимание, что Guava вернет неинтуитивное исключение IllegalArgumentException, если набор результатов имеет более одного результата. (Исключение имеет смысл для клиентов getOnlyElement (), поскольку оно принимает список результатов в качестве аргумента, но менее понятно клиентам getSingleResultOrNull ().)


1

Вот еще одно расширение, на этот раз на Scala.

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

С этим сутенером:

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

Таким образом, все решения «попытаться переписать без исключения» на этой странице имеют небольшую проблему. Либо он не выбрасывает исключение NonUnique, либо не выбрасывает его в некоторых неправильных случаях (см. Ниже).

Я думаю, что правильное решение (возможно) следующее:

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

Он возвращает значение null, если в списке есть 0 элементов, возвращает неуникальный, если в списке есть разные элементы, но не возвращает неуникальный, когда один из ваших выбранных элементов не спроектирован должным образом и возвращает один и тот же объект более одного раза.

Не стесняйтесь комментировать.


Слава Богу, кто-то указал на очевидную истину: если OP вызывает getSingleResult (), он ожидает, что результат будет уникальным, а не просто получить, который окажется первым в (возможно, неупорядоченном) запросе! С Java8 это еще чище:getResultList().stream().distinct().reduce((a, b) -> {throw new NonUniqueResultException();}).orElse(null);
Иларио

1

Посмотрите этот код:

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

Когда findFirst()вызывается, возможно, может возникнуть исключение NullPointerException.

лучший подход:

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


0

Я добился этого, получив список результатов и проверив, пуст ли он

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

Это так раздражает, что getSingleResult()выкидывает исключения

Броски:

  1. NoResultException - если нет результата
  2. NonUniqueResultException - если более одного результата и какое-то другое исключение, о котором вы можете получить дополнительную информацию из их документации

-3

Это работает для меня:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.