JPA getSingleResult () або null


136

У мене є 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.

Дякую

Відповіді:


266

Накидання винятку - це те, як getSingleResult()вказує, що його неможливо знайти. Особисто я не витримую такого типу API. Це змушує обробляти виправдані винятки без реальної вигоди. Вам просто потрібно загортати код у блок-пробний блок.

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


115
Я не згоден, getSingleResult()використовується в таких ситуаціях, як: " Я повністю впевнений, що цей запис існує. Стріляйте, якщо цього немає ". Я не хочу тестувати nullкожен раз, коли я використовую цей метод, тому що я впевнений , що він не поверне його. Інакше це спричиняє чимало котлових та захисних програмувань. І якщо запису насправді не існує (як би протилежне тому, що ми припустили), набагато краще це NoResultExceptionпорівняно з NullPointerExceptionкількома рядками пізніше. Звичайно, мати дві версії getSingleResult()було б дивним, але якщо мені доведеться забрати одну
Томаш Нуркевич

8
@cletus Null дійсно є дійсним значенням повернення для бази даних.
Білл Росмус

12
@TomaszNurkiewicz, це хороший момент. Однак, схоже, має бути якийсь тип "getSingleResultOrNull". Я думаю, ви могли б створити обгортку для таких.
cbmeeks

2
Ось деякі відомості, що стосуються користі виключення, починають викидати з getSingleResult (): Запити можна використовувати для отримання майже нічого, включаючи значення одного стовпця в одному рядку. Якщо getSingleResult () поверне null, ви не можете сказати, чи запит не відповідає жодному рядку, чи відповідність запиту рядку, але вибраний стовпець містить null як його значення. від: stackoverflow.com/a/12155901/1242321
user1242321

5
Він повинен повертати необов’язково <T>. Це хороший спосіб вказати пропущені значення.
Вівек Котарі

33

Я вклав логіку в наступний хелперний метод.

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 () та зафіксувати об’єкт у блоці спробувати, нарешті, і, можливо, просто не вдаватиметься, якщо Query.getFirstResult () поверне щось цікаве.
Патрік Лінскі

саме так ми це втілили в наш проект. Ніколи не було проблем з цією реалізацією
walv

25

Спробуйте це на Java 8:

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

3
Ви можете позбутися від необов’язкового, додавши.orElse(null)
Джастін Роу

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());

15

Я зробив (на Java 8):

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

що ви маєте на увазі під запитом?
Енріко Джурін

Ви маєте на увазі HibernateQuery? Що робити, якщо я хочу використовувати чисту api JPA? Такого методу в javax.persistent.Query немає
Енріко Джурін

2
@EnricoGiurin, я відредагував фрагмент. Робота чудово. Немає спроб лову, а також не чекає list.size. Найприємніше рішення для одного вкладиша.
LovaBill

10

З JPA 2.2 замість .getResultList()та перевіряйте, чи список порожній чи створити потік, ви можете повернути потік та взяти перший елемент.

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

7

Якщо ви хочете використати механізм спробувати / ловити для вирішення цієї проблеми .., то це може бути використане для дії, як би / ще. Я використовував спробу / ловити, щоб додати новий запис, коли не знайшов існуючого.

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

Ось типізована / генерична версія, заснована на реалізації Родріго IronMan:

 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, гарантує повернення лише 1 результату.


4

Тож не робіть цього!

У вас є два варіанти:

  1. Запустіть вибір, щоб отримати COUNT вашого набору результатів, і витягніть дані, лише якщо ця кількість не дорівнює нулю; або

  2. Скористайтеся іншим типом запиту (який отримує набір результатів) і перевірте, чи має він 0 або більше результатів. Він повинен мати 1, тому витягніть це зі своєї колекції результатів, і ви закінчите.

Я б пішов з другою пропозицією за згодою з Клетусом. Це дає кращу ефективність, ніж (потенційно) 2 запити. Також менше роботи.


1
Варіант 3 Спробуйте / ловити NoResultException
Ced

3

Поєднуючи корисні біти існуючих відповідей (обмежуючи кількість результатів, перевіряючи, що результат унікальний) та використовуючи назву методу estabilshed (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).


2

Я вирішив це, використовуючи List<?> myList = query.getResultList();та перевіряючи, чи myList.size()дорівнює нулю.


1

Ось така ж логіка, що запропонована іншими (отримати результатList, повернути його єдиний елемент або нуль), використовуючи 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

Подивіться цей код:

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

Коли findFirst()викликається, можливо, можна кинути NullPointerException.

найкращий підхід:

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


0

Тому все рішення "спробувати переписати без винятку" на цій сторінці має незначну проблему. Або не викидаючи 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;
}

Повернення з нулем, якщо в списку є 0 елементів, повернення ненонічного, якщо в списку є різні елементи, але не повернення невідомості, коли один з обраних вами не розроблений належним чином і повертає той самий об'єкт більше одного разу.

Сміливо коментуйте.


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.