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.

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

Cletus
источник
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();
Импала67
источник
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+.
Дирк Хиллбрехт
17

В Spring есть служебный метод для этого:

TypedQuery<Profile> query = em.createNamedQuery(namedQuery, Profile.class);
...
return org.springframework.dao.support.DataAccessUtils.singleResult(query.getResultList());
Heenenee
источник
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).

at_sof
источник
2

Я решил это, используя List<?> myList = query.getResultList();и проверяя, myList.size()равно ли нулю.

Сергій Катрюк
источник
1

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

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

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

TPDI
источник
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 элементов, возвращает неуникальный, если в списке есть разные элементы, но не возвращает неуникальный, когда один из ваших выбранных элементов не спроектирован должным образом и возвращает один и тот же объект более одного раза.

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

tg44
источник
Слава Богу, кто-то указал на очевидную истину: если 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;
peterzinho16
источник