JPA getSingleResult () ou null

136

J'ai une insertOrUpdateméthode qui insère un Entityquand il n'existe pas ou le met à jour s'il existe. Pour activer cela, je dois findByIdAndForeignKey, s'il retourne nullinsérer sinon, mettre à jour. Le problème est de savoir comment vérifier s'il existe? Alors j'ai essayé getSingleResult. Mais cela lève une exception si le

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

mais getSingleResultjette un Exception.

Merci

Eugène Ramirez
la source

Réponses:

266

Lancer une exception est la façon dont getSingleResult()indique qu'elle est introuvable. Personnellement, je ne supporte pas ce genre d'API. Cela force la gestion des exceptions parasites sans réel avantage. Il vous suffit d'encapsuler le code dans un bloc try-catch.

Vous pouvez également rechercher une liste et voir si elle est vide. Cela ne fait pas exception. En fait, étant donné que vous ne faites pas techniquement une recherche de clé primaire, il peut y avoir plusieurs résultats (même si l'un, les deux ou la combinaison de vos clés ou contraintes étrangères rend cela impossible dans la pratique), c'est probablement la solution la plus appropriée.

cletus
la source
115
Je ne suis pas d'accord, getSingleResult()est utilisé dans des situations comme: " Je suis tout à fait sûr que ce disque existe. Tirez-moi si ce n'est pas le cas ". Je ne veux pas tester nullchaque fois que j'utilise cette méthode car je suis sûr qu'elle ne la retournera pas. Sinon, cela entraîne beaucoup de programmation passe-partout et défensive. Et si le disque n'existe vraiment pas (contrairement à ce que nous avons supposé), il vaut bien mieux l'avoir NoResultExceptionpar rapport à NullPointerExceptionquelques lignes plus tard. Bien sûr, avoir deux versions de getSingleResult()serait génial, mais si je devais en choisir une ...
Tomasz Nurkiewicz
8
@cletus Null est en effet une valeur de retour valide pour une base de données.
Bill Rosmus
12
@TomaszNurkiewicz c'est un bon point. Cependant, il semble qu'il devrait y avoir un certain type de "getSingleResultOrNull". Je suppose que vous pourriez créer un emballage pour un tel.
cbmeeks
2
Voici quelques informations en terme de bénéfice de l'exception de début levé de getSingleResult (): Les requêtes peuvent être utilisées pour récupérer presque tout, y compris la valeur d'une seule colonne dans une seule ligne. Si getSingleResult () renverrait null, vous ne pouviez pas dire si la requête ne correspondait à aucune ligne ou si la requête correspondait à une ligne mais la colonne sélectionnée contient null comme valeur. de: stackoverflow.com/a/12155901/1242321
user1242321
5
Il doit renvoyer facultatif <T>. C'est une bonne manière d'indiquer les valeurs manquantes.
Vivek Kothari
33

J'ai encapsulé la logique dans la méthode d'aide suivante.

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();
    }
}
Eugène Katz
la source
2
Notez que vous pouvez être un peu plus optimal en appelant Query.setMaxResults (1). Malheureusement, puisque Query est avec état, vous voudrez capturer la valeur de Query.getMaxResults () et réparer l'objet dans un bloc try-finally, et peut-être tout simplement échouer si Query.getFirstResult () renvoie quelque chose d'intéressant.
Patrick Linskey
c'est ainsi que nous l'avons implémenté sur notre projet. Jamais eu de problèmes avec cette implémentation
walv
25

Essayez ceci dans Java 8:

Optional first = query.getResultList().stream().findFirst();
Impala67
la source
3
Vous pouvez vous débarrasser de l'Optionnel en ajoutant.orElse(null)
Justin Rowe
24

Voici une bonne option pour faire cela:

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
la source
2
Soigné! J'accepterais TypedQuery<T>cependant, auquel cas le getResultList()est alors déjà correctement tapé comme un List<T>.
Rup
En combinaison avec fetch()l'entité peut ne pas être complètement peuplée. Voir stackoverflow.com/a/39235828/661414
Leukipp
1
C'est une très belle approche. Notez qu'il setMaxResults()a une interface fluide pour que vous puissiez écrire query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Cela devrait être le schéma d'appel le plus efficace de Java 8+.
Dirk Hillbrecht le
17

Spring a une méthode utilitaire pour cela:

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

J'ai fait (en Java 8):

query.getResultList().stream().findFirst().orElse(null);
Zhurov Konstantin
la source
qu'entendez-vous par requête?
Enrico Giurin
Vous voulez dire HibernateQuery? Que faire si je souhaite utiliser l'API JPA pure? Il n'y a pas une telle méthode dans javax.persistence.Query
Enrico Giurin
2
@EnricoGiurin, j'ai édité l'extrait. Fonctionne bien. Pas de try-catch et pas de contrôle de list.size. La meilleure solution de doublure.
LovaBill
10

À partir de JPA 2.2 , au lieu de .getResultList()vérifier si la liste est vide ou de créer un flux, vous pouvez renvoyer le flux et prendre le premier élément.

.getResultStream()
.findFirst()
.orElse(null);
Serafins
la source
7

Si vous souhaitez utiliser le mécanisme try / catch pour gérer ce problème .. alors il peut être utilisé pour agir comme if / else. J'ai utilisé le try / catch pour ajouter un nouvel enregistrement alors que je n'en trouvais pas un existant.

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); 
}
Trieur
la source
6

Voici une version typée / générique, basée sur l'implémentation de Rodrigo 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);
}
Emmanuel Touzery
la source
5

Il existe une alternative que je recommanderais:

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

Cette sauvegarde contre l'exception de pointeur nul, garantit qu'un seul résultat est renvoyé.

as.
la source
4

Alors ne fais pas ça!

Vous avez deux options:

  1. Exécutez une sélection pour obtenir le COUNT de votre jeu de résultats et n'extrayez les données que si ce nombre est différent de zéro; ou

  2. Utilisez l'autre type de requête (qui obtient un jeu de résultats) et vérifiez si elle a 0 ou plusieurs résultats. Il devrait en avoir 1, alors retirez-le de votre collection de résultats et vous avez terminé.

J'irais avec la deuxième suggestion, en accord avec Cletus. Il donne de meilleures performances que (potentiellement) 2 requêtes. Moins de travail aussi.

Carl Smotricz
la source
1
Option 3 Essayez / attrapez NoResultException
Ced
3

En combinant les bits utiles des réponses existantes (limiter le nombre de résultats, vérifier que le résultat est unique) et en utilisant le nom de la méthode estabilshed (Hibernate), on obtient:

/**
 * 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
la source
3

La méthode non documentée uniqueResultOptionaldans org.hibernate.query.Query devrait faire l'affaire. Au lieu d'avoir à attraper un, NoResultExceptionvous pouvez simplement appeler query.uniqueResultOptional().orElse(null).

at_sof
la source
2

J'ai résolu ce problème en utilisant List<?> myList = query.getResultList();et en vérifiant si myList.size()égal à zéro.

Сергій Катрюк
la source
1

Voici la même logique que d'autres suggérées (obtenir le resultList, renvoyer son seul élément ou null), en utilisant Google Guava et un TypedQuery.

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

Notez que Guava renverra l'exception IllegalArgumentException peu intuitive si le jeu de résultats a plus d'un résultat. (L'exception a du sens pour les clients de getOnlyElement (), car elle prend la liste de résultats comme argument, mais est moins compréhensible pour les clients de getSingleResultOrNull ().)

tpdi
la source
1

Voici une autre extension, cette fois dans Scala.

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

Avec ce souteneur:

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
la source
1

Regardez ce code:

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

Quand findFirst()est appelé peut-être peut-être lancé une NullPointerException.

la meilleure approche est:

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

Leandro Ferreira
la source
0

Donc, toute la solution «essayez de réécrire sans exception» dans cette page a un problème mineur. Soit il ne lance pas d'exception NonUnique, ni ne le lance également dans certains cas erronés (voir ci-dessous).

Je pense que la bonne solution est (peut-être) celle-ci:

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

Son retour avec null s'il y a 0 élément dans la liste, retourne non unique s'il y a différents éléments dans la liste, mais ne retourne pas non-unique quand l'un de vos sélections n'est pas correctement conçu et retourne le même objet plus d'une fois.

N'hésitez pas à commenter.

tg44
la source
0

J'ai réalisé cela en obtenant une liste de résultats puis en vérifiant si elle est vide

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

C'est tellement ennuyeux qui getSingleResult()jette des exceptions

Jette:

  1. NoResultException - s'il n'y a pas de résultat
  2. NonUniqueResultException - si plus d'un résultat et une autre exception sur laquelle vous pouvez obtenir plus d'informations à partir de leur documentation
Uchephilz
la source
-3

Cela fonctionne pour moi:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
peterzinho16
la source