JPA getSingleResult() 或 null

Posted

技术标签:

【中文标题】JPA getSingleResult() 或 null【英文标题】:JPA getSingleResult() or null 【发布时间】:2011-01-01 11:01:12 【问题描述】:

我有一个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

谢谢

【问题讨论】:

getSingleResult() 强制您在没有值的情况下使用异常处理,即使没有值是一种常见且自然的情况。最佳实践是异常应该只用于异常情况,而没有值则不是。由于这个原因,很多人不喜欢getSingleResult()。甚至 JPA 诞生的 Hibernate 的作者也批评getSingleResult()。如果你也不喜欢,请点赞:github.com/eclipse-ee4j/jpa-api/issues/298 【参考方案1】:

抛出异常是getSingleResult() 表示找不到它的方式。我个人无法忍受这种 API。它强制进行虚假异常处理而没有真正的好处。您只需将代码包装在 try-catch 块中即可。

或者,您可以查询列表并查看其是否为空。这不会引发异常。实际上,由于您在技术上没有进行主键查找,因此可能会有多个结果(即使一个、两个或您的外键或约束的组合在实践中使这不可能)所以这可能是更合适的解决方案。

【讨论】:

我不同意,getSingleResult() 用于以下情况:“我完全确定这条记录存在。如果不存在,就杀了我”。我不想每次使用此方法时都测试null,因为我确定它不会返回它。否则会导致大量的样板和防御性编程。如果记录确实不存在(与我们假设的相反),那么在几行之后使用NoResultException 比使用NullPointerException 要好得多。当然有两个版本的getSingleResult() 会很棒,但如果我必须选择一个...... @cletus Null 确实是数据库的有效返回值。 @TomaszNurkiewicz 这是一个很好的观点。但是,似乎应该有某种类型的“getSingleResultOrNull”。我想你可以为此创建一个包装器。 这里有一些关于从 getSingleResult() 开始抛出异常的好处的信息:查询可用于检索几乎任何内容,包括单行中单列的值。如果 getSingleResult() 将返回 null,则您无法判断查询是否不匹配任何行,或者查询是否匹配行但所选列包含 null 作为其值。来自:***.com/a/12155901/1242321 它应该返回 Optional。这是指示缺失值的好方法。【参考方案2】:

在 Java 8 中试试这个:

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

【讨论】:

您可以通过添加.orElse(null)来摆脱Optional 可以更好:em.createQuery("...").getResultStream().findFirst().orElse(null);.【参考方案3】:

我将逻辑封装在下面的辅助方法中。

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

【讨论】:

请注意,您可以通过调用 Query.setMaxResults(1) 更加优化。遗憾的是,由于 Query 是有状态的,您需要捕获 Query.getMaxResults() 的值并在 try-finally 块中修复对象,如果 Query.getFirstResult() 返回任何有趣的东西,可能会完全失败。跨度> 这就是我们在项目中实现它的方式。这个实现从来没有任何问题【参考方案4】:

这是一个很好的选择:

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

【讨论】:

整洁!不过,我会接受TypedQuery&lt;T&gt;,在这种情况下,getResultList() 已经正确键入为List&lt;T&gt; 结合fetch(),实体可能没有完全填充。见***.com/a/39235828/661414 这是一个非常好的方法。注意setMaxResults() 有一个流畅的界面,所以你可以写query.setMaxResults(1).getResultList().stream().findFirst().orElse(null)。这应该是 Java 8+ 中最高效的调用方案。【参考方案5】:

Spring 对此有 a utility method:

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

【讨论】:

请记住,当有多个元素时,使用DataAccessUtils.singleResult 会引发异常。【参考方案6】:

我已经完成了(在 Java 8 中):

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

【讨论】:

查询是什么意思? 你是说HibernateQuery?如果我想使用纯 JPA api 怎么办? javax.persistence.Query中没有这样的方法 @EnricoGiurin,我已经编辑了 sn-p。工作正常。没有 try-catch,也没有 list.size 检查。最好的一种衬里解决方案。【参考方案7】:

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

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

【讨论】:

或简单地.getResultStream().findFirst()Optional【参考方案8】:

如果你想使用 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); 

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

我会推荐一个替代方案:

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

这可以防止空指针异常,保证只返回 1 个结果。

【讨论】:

【参考方案11】:

所以不要那样做!

你有两个选择:

    运行选择以获取结果集的 COUNT,并且仅当此计数不为零时才拉入数据;或

    使用另一种查询(获取结果集)并检查它是否有 0 个或多个结果。它应该有 1,所以把它从你的结果集合中拉出来,你就完成了。

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

【讨论】:

选项 3 尝试/捕获 NoResultException【参考方案12】:

结合现有答案的有用位(限制结果数量,检查结果是否唯一)并使用已建立的方法名称(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);

【讨论】:

【参考方案13】:

org.hibernate.query.Query 中未记录的方法uniqueResultOptional 应该可以解决问题。您不必捕获NoResultException,只需调用query.uniqueResultOptional().orElse(null)

【讨论】:

【参考方案14】:

我通过使用 List&lt;?&gt; myList = query.getResultList(); 并检查 myList.size() 是否等于 0 解决了这个问题。

【讨论】:

【参考方案15】:

看看这段代码:

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

findFirst() 被调用时可能会抛出 NullPointerException。

最好的方法是:

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

【讨论】:

【参考方案16】:

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

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

请注意,如果结果集有多个结果,Guava 将返回不直观的 IllegalArgumentException。 (该异常对 getOnlyElement() 的客户端有意义,因为它将结果列表作为其参数,但对 getSingleResultOrNull() 的客户端来说不太容易理解。)

【讨论】:

【参考方案17】:

这是另一个扩展,这次是在 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)

【讨论】:

【参考方案18】:

因此,此页面中的所有“尝试无例外地重写”解决方案都有一个小问题。它要么不抛出 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,如果列表中有不同的元素,则返回非唯一,但当您的选择之一设计不正确并返回同一对象多次时,则不返回非唯一。

欢迎评论。

【讨论】:

感谢上帝,有人指出了一个显而易见的事实:如果 OP 调用 getSingleResult() 他期望结果是唯一的,而不是仅仅得到恰好是(可能是无序的)查询中的第一个结果!使用 Java8 会更干净:getResultList().stream().distinct().reduce((a, b) -&gt; throw new NonUniqueResultException();).orElse(null);【参考方案19】:

我通过获取结果列表然后检查它是否为空来实现这一点

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

getSingleResult() 抛出异常太烦人了

投掷:

    NoResultException - 如果没有结果 NonUniqueResultException - 如果有多个结果 以及其他一些例外情况,您可以从他们的文档中获得更多信息

【讨论】:

【参考方案20】:

如果您可以使用新的 JPA 功能,我更喜欢 @Serafins 的回答,但这是一种相当直接的方法,我很惊讶这里之前没有提到过:

    try 
        return (Profile) query.getSingleResult();
     catch (NoResultException ignore) 
        return null;
    

【讨论】:

【参考方案21】:

这对我有用:

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

【讨论】:

以上是关于JPA getSingleResult() 或 null的主要内容,如果未能解决你的问题,请参考以下文章

getSingleResult 在休眠中返回本地标量查询的代理

JPA createNamedQuery 语法

getSingleResult() 改变 count() 查询

使用Spring Data JPA查询时,报result returns more than one elements异常

在 getSingleResult 聚合中处理 null 的正确方法

JPA 的 n+1 问题是不是与 @OneToMany 或 @ManyToOne 或两者有关?