【问题标题】:JPA getSingleResult() or nullJPA getSingleResult() 或 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

标签: java jpa


【解决方案1】:

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

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

【讨论】:

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

【讨论】:

【解决方案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】:

        所以不要那样做!

        你有两个选择:

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

        2. 使用另一种查询(获取结果集)并检查它是否有 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() 抛出异常太烦人了

                    投掷:

                    1. NoResultException - 如果没有结果
                    2. 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;
                        

                        【讨论】:

                          猜你喜欢
                          • 2012-01-19
                          • 1970-01-01
                          • 2013-08-24
                          • 2020-01-30
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-11-07
                          • 1970-01-01
                          • 2017-09-19
                          相关资源
                          最近更新 更多