JPA getSingleResult () o nulo


136

Tengo un insertOrUpdatemétodo que inserta un Entitycuando no existe o lo actualizo si lo hace. Para habilitar esto, tengo que hacerlo findByIdAndForeignKey, si devuelve nullinsertar, si no, actualizar. El problema es cómo verifico si existe. Entonces lo intenté getSingleResult. Pero arroja una excepción si el

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

pero getSingleResultlanza un Exception.

Gracias

Respuestas:


266

Lanzar una excepción es cómo getSingleResult()indica que no se puede encontrar. Personalmente no soporto este tipo de API. Fuerza el manejo de excepciones espurias sin beneficio real. Solo tiene que envolver el código en un bloque try-catch.

Alternativamente, puede consultar una lista y ver si está vacía. Eso no arroja una excepción. En realidad, dado que técnicamente no está haciendo una búsqueda de clave primaria, podría haber múltiples resultados (incluso si uno, ambos o la combinación de sus claves externas o restricciones lo hacen imposible en la práctica), por lo que esta es probablemente la solución más adecuada.


115
No estoy de acuerdo, getSingleResult()se usa en situaciones como: " Estoy totalmente seguro de que este registro existe. Dispárame si no existe ". No quiero probar nullcada vez que uso este método porque estoy seguro de que no lo devolverá. De lo contrario, causa una gran cantidad de programación repetitiva y defensiva. Y si el registro realmente no existe (a diferencia de lo que asumimos), es mucho mejor haberlo NoResultExceptioncomparado con NullPointerExceptionalgunas líneas más adelante. Por supuesto, tener dos versiones de getSingleResult()sería increíble, pero si tengo que elegir una ...
Tomasz Nurkiewicz

8
@cletus Null es de hecho un valor de retorno válido para una base de datos.
Bill Rosmus

12
@TomaszNurkiewicz ese es un buen punto. Sin embargo, parece que debería haber algún tipo de "getSingleResultOrNull". Supongo que podrías crear un envoltorio para eso.
cbmeeks

2
Aquí hay alguna información en términos del beneficio de la excepción que se inicia desde getSingleResult (): las consultas se pueden usar para recuperar casi cualquier cosa, incluido el valor de una sola columna en una sola fila. Si getSingleResult () devolvería nulo, no podría saber si la consulta no coincidió con ninguna fila o si la consulta coincidió con una fila pero la columna seleccionada contiene nulo como su valor. de: stackoverflow.com/a/12155901/1242321
user1242321

55
Debería devolver Opcional <T>. Esa es una buena manera de indicar valores perdidos.
Vivek Kothari

33

Encapsulé la lógica en el siguiente método auxiliar.

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
Tenga en cuenta que puede ser un poco más óptimo llamando a Query.setMaxResults (1). Lamentablemente, dado que Query tiene estado, querrá capturar el valor de Query.getMaxResults () y arreglar el objeto en un bloque try-finally, y tal vez simplemente falle por completo si Query.getFirstResult () devuelve algo interesante.
Patrick Linskey,

así es como lo implementamos en nuestro proyecto. Nunca tuve problemas con esta implementación
walv

25

Prueba esto en Java 8:

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

3
Puede deshacerse de lo opcional agregando.orElse(null)
Justin Rowe, el

24

Aquí hay una buena opción para hacer esto:

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
¡Ordenado! Sin TypedQuery<T>embargo, aceptaría , en cuyo caso, el getResultList()ya está correctamente escrito como a List<T>.
Rup

En combinación con fetch()la entidad podría no estar completamente poblada. Ver stackoverflow.com/a/39235828/661414
Leukipp

1
Este es un enfoque muy agradable. Tenga en cuenta que setMaxResults()tiene una interfaz fluida para que pueda escribir query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Este debería ser el esquema de llamadas más eficiente en Java 8+.
Dirk Hillbrecht

17

Spring tiene un método de utilidad para esto:

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

15

He hecho (en Java 8):

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

¿Qué quieres decir con consulta?
Enrico Giurin el

¿Te refieres a HibernateQuery? ¿Qué sucede si quiero usar la API pura de JPA? No existe tal método en javax.persistence.Query
Enrico Giurin

2
@EnricoGiurin, he editado el fragmento. Trabaja bien. Sin try-catch, y sin verificación de list.size. La mejor solución de un revestimiento.
LovaBill

10

Desde JPA 2.2 , en lugar de .getResultList()verificar si la lista está vacía o crear una secuencia, puede devolver la secuencia y tomar el primer elemento.

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

7

Si desea utilizar el mecanismo try / catch para manejar este problema ... entonces puede usarse para actuar como si / si no. Utilicé try / catch para agregar un nuevo registro cuando no encontré uno existente.

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

Aquí hay una versión tipificada / genérica, basada en la implementación 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);
}

5

Hay una alternativa que recomendaría:

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

Esta protección contra la excepción de puntero nulo garantiza que solo se devuelva 1 resultado.


4

¡Entonces no hagas eso!

Tienes dos opciones:

  1. Ejecute una selección para obtener el CONTEO de su conjunto de resultados y solo extraiga los datos si este conteo no es cero; o

  2. Use el otro tipo de consulta (que obtiene un conjunto de resultados) y verifique si tiene 0 o más resultados. Debería tener 1, así que retíralo de tu colección de resultados y listo.

Me gustaría ir con la segunda sugerencia, de acuerdo con Cletus. Ofrece un mejor rendimiento que (potencialmente) 2 consultas. También menos trabajo.


1
Opción 3 Prueba / captura NoResultException
Ced

3

Combinando los bits útiles de las respuestas existentes (limitando el número de resultados, verificando que el resultado sea único) y usando el nombre del método estabilshed (Hibernate), obtenemos:

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

El método no documentado uniqueResultOptionalen org.hibernate.query.Query debería hacer el truco. En lugar de tener que atrapar a un NoResultExceptionsolo puede llamar query.uniqueResultOptional().orElse(null).



1

Aquí está la misma lógica que otros sugirieron (obtenga la lista de resultados, devuelva su único elemento o nulo), utilizando Google Guava y TypedQuery.

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

Tenga en cuenta que Guava devolverá la IllegalArgumentException no intuitiva si el conjunto de resultados tiene más de un resultado. (La excepción tiene sentido para los clientes de getOnlyElement (), ya que toma la lista de resultados como argumento, pero es menos comprensible para los clientes de getSingleResultOrNull ()).


1

Aquí hay otra extensión, esta vez en Scala.

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

Con este chulo:

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

Mira este código:

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

Cuando findFirst()se llama tal vez se puede lanzar una NullPointerException.

El mejor enfoque es:

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


0

Por lo tanto, toda la solución "tratar de reescribir sin excepción" en esta página tiene un problema menor. O no arroja una excepción no única, ni la arroja en algunos casos incorrectos también (ver más abajo).

Creo que la solución adecuada es (tal vez) esto:

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

Se devuelve con nulo si hay 0 elementos en la lista, se devuelve no único si hay elementos diferentes en la lista, pero no se devuelve no único cuando uno de los seleccionados no está diseñado correctamente y devuelve el mismo objeto más de una vez.

Siéntase libre de comentar.


0

Lo logré obteniendo una lista de resultados y luego verificando si está vacía

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

Es tan molesto que getSingleResult()arroja excepciones

Lanza

  1. NoResultException - si no hay resultado
  2. NonUniqueResultException: si hay más de un resultado y alguna otra excepción de la que puede obtener más información de su documentación

-3

Eso me funciona:

Optional<Object> opt = Optional.ofNullable(nativeQuery.getSingleResult());
return opt.isPresent() ? opt.get() : null;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.