原文:JPA implementation patterns: Retrieving entities
作者:Vincent Partington
出处:http://blog.xebia.com/2009/04/03/jpa-implementation-patterns-retrieving-entities/
上周我们谈论了如何保存实体,而且一旦保存了实体,我们也就希望能够检索它。与管理双向关联和保存实体相比,检索实体实在是相当的简单,如此简单以至于我都怀疑在写本篇博客时能够有多少可说的要点。不过,我们在编写本文中的代码时确实是用到了一些很好的模式,而且我很有兴趣听到那些你用来检索实体的模式,所以,让我们从这里开始JPA实施模式系列中的下一节内容。
基本上,JPA有两种检索实体的方法:
EntityManager.find根据实体的id来查找实体,当实体不存在时,返回null。
如果把使用Java持久查询语言(Java Persistence Query Language)指定的查询串传给EntityManager.createQuery的话,那么它会返回一个实体列表或者单个实体。
Query对象也可以通过引用命名查询(使用EntityManager.createNamedQuery)来创建,或者是传入SQL查询(使用三个各具特色的EntityManager.createNativeQuery中的一个)。而且与其名字暗含的意思不一,Query也可以用来执行更新或者删除语句。
命名查询可能看起来像是一种保持查询与其所查询的实体之间关系的很好的方式,不过我发现这种方式并不是特别有效。大多数的查询需要使用Query.setParameter的各种不同形式来设置参数,把查询和设置参数的代码放在一起能够使得这两者更易于理解,这就是为什么我在DAO中把它们放在一起并避免使用命名查询的原因。
我发现的一种有用的约定是区分对待查找(finding)实体和获取(getting)实体的处理,在前一种情况下,当实体没能被找到时返回null,而在后一种情况下则是抛出异常。在代码预期出现实体的地方使用后一种方法来防止随后会出现NullPointerExceptions被弹出的情况。
通过id查找和获取单个实体
在前面几篇博客之一中我们曾讨论过的JpaDao基础类的这一模式的一个实现,其看上去类似这样(为了对比,其中包括了查找方法):
public E findById(K id) {
return entityManager.find(entityClass, id);
}
public E getById(K id) throws EntityNotFoundException {
E entity = entityManager.find(entityClass, id);
if (entity == null) {
throw new EntityNotFoundException(
"Entity " + entityClass.getName() + " with id " + id + " not found");
}
return entity;
}
当然还需要把这一新的方法添加到Dao接口中:
E getById(K id);
使用查询来查找和获取单个实体
在使用查询来查找单个实体时,可以做出类似的区分,以下的findOrderSubmittedAt方法在查询未能找到实体时返回null,getOrderSubmittedAt方法则抛出NoResultException异常。如果有一个以上的结果返回的话,以上两种方法都会抛出NonUniqueResultException异常。为了保持getOrderSubmittedAt方法与findById方法之间的一致,可以把NoResultException映射成EntityNotFoundException,不过既然两者都是非受查的异常,所以这不是一定需要做的。
由于这些方法只适用于Order对象,那么JpaOrderDao中的一部分就是这样的:
public Order findOrderSubmittedAt(Date date) throws NonUniqueResultException {
Query q = entityManager.createQuery(
"SELECT e FROM " + entityClass.getName() + " e WHERE date = :date_at");
q.setParameter("date_at", date);
try {
return (Order) q.getSingleResult();
} catch (NoResultException exc) {
return null;
}
}
public Order getOrderSubmittedAt(Date date) throws NoResultException, NonUniqueResultException {
Query q = entityManager.createQuery(
"SELECT e FROM " + entityClass.getName() + " e WHERE date = :date_at");
q.setParameter("date_at", date);
return (Order) q.getSingleResult();
}
把正确的方法添加到OrderDao接口中的工作就作为练习留给读者来完成好了。
使用查询来查找多个实体
当然我们也希望能够查找多个实体,在这种情况下,我发现在获取(getting)和查找(finding)之间不用做出什么区分,findOrdersSubmittedSince方法仅是返回被找到的实体的一个列表,该列表可以是包含零个、一个或者多个实体,参见下面的代码:
public List<Order> findOrdersSubmittedSince(Date date) {
Query q = entityManager.createQuery(
"SELECT e FROM " + entityClass.getName() + " e WHERE date >= :date_since");
q.setParameter("date_since", date);
return (List<Order>) q.getResultList();
}
有眼力的读者会注意到,该方法已经在JpaOrderDao的第一个版本中出现过了。
因此,虽然检索实体的工作很简单,但在实现fander方法和getter方法时还是有些模式可依据的,当然,我很有兴趣了解你在代码中是如何处理这些情况的。
附:JPA 1.0还不支持,不过JPA2.0会包括一个Criteria API,Criteria API允许动态地建立JPA查询,Criteria查询比串查询更灵活,可以根据搜索表单的输入来建立它们,并且因为是使用领域对象来定义它们,所以它们作为到领域对象的引用,可以被自动重构,更易于维护。遗憾的是,Criteria API需要通过名称来引用实体的属性,所以在重新命名属性时,IDE不会起到帮助作用。