JPA getSingleResult () ou null

136

Eu tenho um insertOrUpdatemétodo que insere um Entityquando não existe ou atualizá-lo, se existir. Para habilitar isso, eu tenho que findByIdAndForeignKey, se ele retornou nullinserir, se não for, atualizar. O problema é como verifico se ele existe? Então eu tentei getSingleResult. Mas lança uma exceção se o

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

mas getSingleResultjoga um Exception.

obrigado

Eugene Ramirez
fonte

Respostas:

266

Lançar uma exceção é como getSingleResult()indica que ela não pode ser encontrada. Pessoalmente, não suporto esse tipo de API. Força o tratamento de exceções espúrias sem nenhum benefício real. Você só precisa agrupar o código em um bloco try-catch.

Como alternativa, você pode consultar uma lista e ver se está vazia. Isso não lança uma exceção. Na verdade, como você não está fazendo uma pesquisa de chave primária tecnicamente, pode haver vários resultados (mesmo que um, ambos ou a combinação de suas chaves ou restrições estrangeiras tornem isso impossível na prática), portanto essa é provavelmente a solução mais apropriada.

cleto
fonte
115
Não concordo, getSingleResult()é usado em situações como: " Estou totalmente certo de que esse registro existe. Atire em mim se não existir ". Não quero testar nulltodas as vezes que uso esse método porque tenho certeza de que ele não será devolvido. Caso contrário, isso causa muitos clichês e programação defensiva. E se o registro realmente não existir (como oposto ao que assumimos), é muito melhor NoResultExceptioncompará-lo com NullPointerExceptionalgumas linhas mais tarde. É claro que ter duas versões getSingleResult()seria fantástico, mas se eu tiver que pegar um ...
Tomasz Nurkiewicz
8
@cletus Null é realmente um valor de retorno válido para um banco de dados.
Bill Rosmus
12
@TomaszNurkiewicz esse é um bom ponto. No entanto, parece que deve haver algum tipo de "getSingleResultOrNull". Eu acho que você poderia criar um invólucro para isso.
cbmeeks
2
Aqui estão algumas informações sobre o benefício da exceção iniciadas por getSingleResult (): As consultas podem ser usadas para recuperar quase tudo, incluindo o valor de uma única coluna em uma única linha. Se getSingleResult () retornar nulo, não será possível saber se a consulta não corresponde a nenhuma linha ou se a consulta corresponde a uma linha, mas a coluna selecionada contém nulo como valor. De: stackoverflow.com/a/12155901/1242321
user1242321
5
Ele deve retornar <T> opcional. Essa é uma boa maneira de indicar valores ausentes.
Vivek Kothari
33

Encapsulei a lógica no seguinte 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();
    }
}
Eugene Katz
fonte
2
Observe que você pode ser um pouco mais otimizado chamando Query.setMaxResults (1). Infelizmente, como Query é stateful, você deseja capturar o valor de Query.getMaxResults () e consertar o objeto em um bloco try-finally, e talvez apenas falhe completamente se Query.getFirstResult () retornar algo interessante.
Patrick Linskey
é assim que implementamos em nosso projeto. Nunca teve problemas com esta implementação
walv
25

Tente isso no Java 8:

Optional first = query.getResultList().stream().findFirst();
Impala67
fonte
3
Você pode se livrar do Opcional adicionando.orElse(null)
Justin Rowe
24

Aqui está uma boa opção para fazer isso:

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
fonte
2
Arrumado! Eu aceitaria TypedQuery<T>, nesse caso, o getResultList()já está corretamente digitado como a List<T>.
Rup
Em combinação com fetch()a entidade pode não estar completamente preenchida. Veja stackoverflow.com/a/39235828/661414
Leukipp
1
Esta é uma abordagem muito agradável. Observe que setMaxResults()possui uma interface fluente para que você possa escrever query.setMaxResults(1).getResultList().stream().findFirst().orElse(null). Esse deve ser o esquema de chamada mais eficiente no Java 8+.
Dirk Hillbrecht
17

O Spring possui um método utilitário para isso:

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

Eu fiz (no Java 8):

query.getResultList().stream().findFirst().orElse(null);
Zhurov Konstantin
fonte
o que você quer dizer com consulta?
Enrico Giurin 29/11
Você quer dizer HibernateQuery? E se eu quiser usar a API JPA pura? Não existe esse método no javax.persistence.Query
Enrico Giurin
2
@EnricoGiurin, editei o snippet. Funciona bem. Nenhuma tentativa de captura e nenhuma verificação de list.size. A melhor solução de um revestimento.
LovaBill 17/05/19
10

No JPA 2.2 , em vez de .getResultList()verificar se a lista está vazia ou criando um fluxo, você pode retornar o fluxo e obter o primeiro elemento.

.getResultStream()
.findFirst()
.orElse(null);
Serafins
fonte
7

Se você deseja usar o mecanismo try / catch para lidar com esse problema .. ele pode ser usado para agir como se / outro. Usei o try / catch para adicionar um novo registro quando não encontrei um 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); 
}
Classificador
fonte
6

Aqui está uma versão digitada / genérica, com base na implementação 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
fonte
5

Existe uma alternativa que eu recomendaria:

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

Isso protege contra a exceção do ponteiro nulo, garante que apenas 1 resultado seja retornado.

ases.
fonte
4

Então não faça isso!

Você tem duas opções:

  1. Execute uma seleção para obter a COUNT do seu conjunto de resultados e somente extraia os dados se essa contagem for diferente de zero; ou

  2. Use o outro tipo de consulta (que obtém um conjunto de resultados) e verifique se possui 0 ou mais resultados. Ele deve ter 1, então retire-o da sua coleção de resultados e pronto.

Eu aceitaria a segunda sugestão, de acordo com Cletus. Oferece melhor desempenho do que (potencialmente) 2 consultas. Também menos trabalho.

Carl Smotricz
fonte
1
Opção 3 Tente / capturando NoResultException
Ced
3

Combinando os bits úteis das respostas existentes (limitando o número de resultados, verificando se o resultado é único) e usando o nome do método estabelecido (Hibernate), obtemos:

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

O método não documentado uniqueResultOptionalem org.hibernate.query.Query deve fazer o truque. Em vez de ter que pegar um, NoResultExceptionvocê pode simplesmente ligar query.uniqueResultOptional().orElse(null).

at_sof
fonte
2

Eu resolvi isso usando List<?> myList = query.getResultList();e verificando se myList.size()é igual a zero.

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

Aqui está a mesma lógica sugerida por outras pessoas (obtenha o resultadoList, retorne seu único elemento ou nulo), usando o Google Guava e um TypedQuery.

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

Observe que o Guava retornará a IllegalArgumentException não intuitiva se o conjunto de resultados tiver mais de um resultado. (A exceção faz sentido para os clientes de getOnlyElement (), pois leva a lista de resultados como argumento, mas é menos compreensível para os clientes de getSingleResultOrNull ().)

tpdi
fonte
1

Aqui está outra extensão, desta vez em Scala.

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

Com este cafetão:

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
fonte
1

Veja este código:

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

Quando findFirst()é chamado, talvez seja possível lançar uma NullPointerException.

a melhor abordagem é:

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

Leandro Ferreira
fonte
0

Portanto, toda a solução "tentar reescrever sem exceção" nesta página tem um problema menor. Não está lançando a exceção NonUnique, nem em alguns casos errados (veja abaixo).

Penso que a solução adequada é (talvez) esta:

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

É retornado com null se houver 0 elemento na lista, retornando não exclusivo se houver elementos diferentes na lista, mas não retornando não exclusivo quando um dos seus selecionados não foi projetado corretamente e retorna o mesmo objeto mais de uma vez.

Sinta-se livre para comentar.

tg44
fonte
0

Consegui isso obtendo uma lista de resultados e verificando se ela está vazia

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

É tão irritante que getSingleResult()lança exceções

Lances:

  1. NoResultException - se não houver resultado
  2. NonUniqueResultException - se houver mais de um resultado e alguma outra exceção, você poderá obter mais informações na documentação deles
Uchephilz
fonte
-3

Isso funciona para mim:

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