JPA getSingleResult()或为null


136

我有一个在不存在时insertOrUpdate插入Entity或在存在时更新的方法。要启用此功能,我必须执行findByIdAndForeignKey,如果返回的话则返回nullinsert,然后更新。问题是如何检查它是否存在?所以我尝试了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

谢谢

Answers:


266

引发异常是如何getSingleResult()指示找不到该异常。我个人不能忍受这种API。它强制虚假异常处理没有任何实际好处。您只需要将代码包装在try-catch块中即可。

或者,您可以查询列表,并查看其是否为空。那也不例外。实际上,由于从技术上讲,您没有进行主键查找,因此可能会有多个结果(即使外键或约束之一,两者或两者的结合使得在实践中都不可能做到这一点),所以这可能是更合适的解决方案。


115
我不同意,getSingleResult()它用在以下情况中:“ 我完全确定该记录存在。如果不存在,请枪Shoot我。” 我不想null每次使用此方法都进行测试,因为我确定它不会返回它。否则,它将导致大量重复样板和防御性编程。而且,如果该记录确实不存在(与我们所假设的相反),那么NoResultExceptionNullPointerException以后的几行相比要好得多。当然拥有两个版本getSingleResult()会很棒,但是如果我必须拿起一个版本...
Tomasz Nurkiewicz

8
@cletus Null确实是数据库的有效返回值。
Bill Rosmus 2012年

12
@TomaszNurkiewicz很好。但是,似乎应该有某种类型的“ getSingleResultOrNull”。我想您可以为此创建一个包装器。
cbmeeks

2
从getSingleResult()开始引发异常方面,这是一些信息:查询可用于检索几乎所有内容,包括单行中单列的值。如果getSingleResult()返回null,则无法确定查询是否与任何行都不匹配,或者查询是否与行匹配,但所选列包含null作为其值。来自:stackoverflow.com/a/12155901/1242321
user1242321

5
它应该返回Optional <T>。这是指示缺失值的好方法。
Vivek Kothari

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()的值并在try-finally块中修复该对象,如果Query.getFirstResult()返回任何有趣的信息,则可能会完全失败。
Patrick Linskey 2011年

这就是我们在项目中实现它的方式。此实现从未有任何问题
徘徊

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 '16

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

查询是什么意思?
Enrico Giurin

您是说HibernateQuery吗?如果我想使用纯JPA api怎么办?javax.persistence.Query中没有这样的方法
Enrico Giurin

2
@EnricoGiurin,我已经编辑了代码段。干得好 没有尝试捕获,也没有list.size检查。最好的一种内衬解决方案。
LovaBill

10

从JPA 2.2开始.getResultList()您可以返回stream并获取第一个元素,而不是检查list是否为空或创建流。

.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

这是基于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

有一种我建议的替代方法:

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,因此将其从结果集合中拉出就可以了。

我同意第二个建议,与Cletus一致。它提供了比(可能)2个查询更好的性能。还少工作。


1
选项3尝试/捕获NoResultException
2013年

3

结合现有答案的有用位(限制结果的数量,检查结果是否唯一)并使用稳定的方法名称(休眠),我们得到:

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

uniqueResultOptionalorg.hibernate.query.Query中未记录的方法应该可以解决问题。不必挂NoResultException电话,您可以致电query.uniqueResultOptional().orElse(null)



1

这与其他人使用Google Guava和TypedQuery建议的逻辑相同(获取resultList,返回其唯一元素或为null)。

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

请注意,如果结果集有多个结果,则番石榴将返回直观的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个元素,则以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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.