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在 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<T>
,在这种情况下,getResultList()
已经正确键入为List<T>
。
结合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<?> 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) -> 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 在休眠中返回本地标量查询的代理
getSingleResult() 改变 count() 查询
使用Spring Data JPA查询时,报result returns more than one elements异常