原文:JPA implementation patterns: Data Access Objects
作者:Vincent Partington
出处:http://blog.xebia.com/2009/03/09/jpa-implementation-patterns-data-access-objects/
JPA,Java Persistence API的简称,是Java EE 5规范的一部分,并且已经由Hibernate、TopLink、EclipseLink、OpenJPA以及其他的一些对象关系映射(ORM)框架实现。因为JPA的最初目的是作为EJB 3.0规范的组成部分,所以你可以在EJB 3.0应用中使用它,不过在EJB 3.0范围之外它同样好使,例如,可以用在Spring应用中。甚至连Gavin King,Hibernate的设计者也在Hibernate in Action的第二版,又名为Java Persistence with Hibernate一书中建议使用JPA。很显然,JPA已被普遍使用。
一旦你克服了自己对注解(annotation)的恐惧;-),你就会发现外面有大量的资料文献可用来解释API内部的对象和方法、对象一起工作的方式以及如何把它们付诸实现。当你进行hello world类型的程序编写的时候,一切都似乎相当直接明了,但在你开始编写自己的第一个实际应用时,你就会发现,事情并非如此简单。JPA提供的抽象泄漏得厉害,已经影响到了应用的大部分而不仅仅只是影响了数据访问对象(DAO)和领域对象,你需要决定如何处理事务、延迟加载、游离对象(考虑到web框架)、继承等等,其结果是,那些书和文章在这里都不能真正帮到你。
至少,这是我第一次真正开始使用JPA时发现的情况,在未来的几周内,我打算讨论我会面临的选择和我所做的决定,以及为什么我会做出这样的决定。当我完成讨论后,我们会得到一些收获,我愿意不太谦虚地称这些收获为JPA的实施模式。
我们真的需要DAO吗?
那么,让我们从这件事情开始,那就是你可能会开始编写自己的第一个JPA应用:数据访问对象(DAO)。恰好在开始之前,我们需要解决这样一个有趣的问题:在使用JPA的时候,是否需要DAO?一年多之前的讨论结果是“视情况而定”,既然非常难于就这样的一个结论进行争辩J,我想坚持这样的一种想法,即DAO在JPA应用中确实有着自己的位置。可以认为DAO只是在JPA之上提供了很薄的一层,但是其更为重要的是为每个实体类型提供了一个DAO,这一做法带来了这些好处:
不必在每次储存或者加载数据的时候,都要挑选正确的EntityManager方法,一旦决定了使用那一个之后,你和你的整个团队都能够很容易做到坚持这一选择。
可以禁止某些实体类型的某些操作,例如,你可能永远都不希望代码删除日志条目,那么在使用DAO时,只要不把remove方法添加到你的LogEntry DAO中就可以了。
从理论上来说,通过使用DAO你可以切换到另外一个持久性系统(例如普通的JDBC或者iBATIS),但是因为JPA是这样一个有泄漏的抽象,所以我认为甚至对于一个略微复杂的系统来说,这都是不存在切合实际的可能性的。不过,在能够加入跟踪功能或者保持性能统计的地方,你确实是可以获得一个单一的切入点。
可以把所有的查询都集中到某个实体类型上而不是让他们遍布在代码中,可以使用命名查询来保存实体类型的查询,不过仍然需要在一些适中的地方设置正确的参数,把查询、设置参数的代码和到正确返回类型的强制类型转换都放入到DAO中似乎是一件更简单的事情,例如:
public List<ChangePlan> findExecutingChangePlans() {
Query query = entityManager.createQuery(
"SELECT plan FROM ChangePlan plan where plan.state = 'EXECUTING'");
return (List<ChangePlan>) query.getResultList();
}
因此,当你决定要使用DAO时,你如何着手编写它们呢?Spring的JpaTemplate的Javadoc中突出强调(粗体)的注释似乎表明,该特定类在使用上并没有太多的用途,这也使得JpaDaoSupport变成了多余的。作为替代,你可以把自己的JPA DAO编写成使用@PersistenceContext注解来获取EntityManager引用的POJO,它可用在EJB3.0容器中,如果在Spring的上下文中添加了PersistenceAnnotationBeanPostProcessor的话,那么它同样可以用在Spring 2.0以及之上的版本中。
类型安全的泛型DAO模式
因为每个DAO都与其他的DAO共享许多功能,所以有必要创建一个包含了共享功能的基类,然后每个具体的DAO作为子类继承于该基类。网上有许多关于这样一种类型安全的泛型DAO模式的博客文章,你甚至可以从Google Code代码站点上下载一些代码。当把来自所有这些来源的元素综合到一起时,我们得到了以下这一DAO的JPA实施模式:
实体类
比方说,我们希望持久以下这一Order类:
@Entity
@Table(name = "ORDERS")
public class Order {
@Id
@GeneratedValue
private int id;
private String customerName;
private Date date;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getCustomerName() { return customerName; }
public void setCustomerName(String customerName) { this.customerName = customerName; }
public Date getDate() { return date; }
public void setDate(Date date) { this.date = date;}
}
不要太担心该类的细节,我们会采用其他的JPA实施模式来修正这些细微之处。这里使用了@Table注解是因为ORDER在SQL中是一个保留关键字。
DAO接口
首先定义一个通用的DAO接口,该接口带有我们希望所有DAO共享的方法:
public interface Dao<K, E> {
void persist(E entity);
void remove(E entity);
E findById(K id);
}
第一个类型参数K用来作为键的类型,第2个类型参数E是实体的类型,紧接着基本的persist、remove和findById方法之后,你可能还希望添加一个List findAll()方法。不过与实体类自身的定义相同,我们会用随后的JPA实施模式来修正这些DAO方法。
然后我们为每个要持久的实体类型定义一个子接口,加入任何我们想要的实体特有的方法。例如,如果我们希望能够查询所有自某个特定日期以来已经添加的订单(order)的话,就可以加入这样的一个方法:
public interface OrderDao extends Dao<Integer, Order> {
List<Order> findOrdersSubmittedSince(Date date);
}
基本DAO的实现
第三步是创建一个基础的JPA DAO实现,初步实现我们在步骤1中创建的标准Dao接口的所有方法。
public abstract class JpaDao<K, E> implements Dao<K, E> {
protected Class<E> entityClass;
@PersistenceContext
protected EntityManager entityManager;
public JpaDao() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
this.entityClass = (Class<E>) genericSuperclass.getActualTypeArguments()[1];
}
public void persist(E entity) { entityManager.persist(entity); }
public void remove(E entity) { entityManager.remove(entity); }
public E findById(K id) { return entityManager.find(entityClass, id); }
}
大部分的实现都相当直接简单,但是有几点要注意:
JpaDao的构造函数包括了我的同事Arjan Blokzijl提议的使用反射来获得实体类的方法。
@PersistenceContext注解会引发EJB 3.0容器或者Spring注入实体管理器。
entityManager和entityClass域被定义成是protected的,这样的话子类,例如特定的DAO实现就能够访问它们。
特定DAO的实现
最后我们来创建一个具体的DAO实现,其扩展了基础的JPA DAO类并实现了特定的DAO接口:
public class JpaOrderDao extends JpaDao<Integer, Order> implements OrderDao {
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();
}
}
使用DAO
如何获得一个到OrderDao实例的引用取决于我们是使用EJB 3.0还是使用Spring,在EJB 3.0中,我们会使用这样的注解:
@EJB(name="orderDao")
private OrderDao orderDao;
而在Spring中我们可以使用XML bean文件或者使用这种方式的自动绑定:
@Autowired
public OrderDao orderDao;
不管是哪种方式,一旦有到DAO的引用,我们就可以这样来使用它:
Order o = new Order();
o.setCustomerName("Peter Johnson");
o.setDate(new Date());
orderDao.persist(o);
不过我们还可以使用添加到OrderDao接口的实体特有的查询:
List<Order> orders = orderDao.findOrdersSubmittedSince(date);
for (Order each : orders) {
System.out.println("order id = " + each.getId());
}
通过使用这一类型安全的DAO模式,我们获得了如下好处:
客户端代码没有直接依赖于JPA的API。
通过使用泛型保证了类型的安全,所有需要完成的强制类型转换都在DAO的实现中被处理了。
在一个合理的地方汇集了所有的实体特有的JPA代码。
可在同一个场所添加事务标志、调试和配置描述等,诚然如我们稍后会见到的那样,我们也需要在应用的其他部分中添加事务标志。
测试数据库访问代码时,只要测试一个类就够了,在后面的某个JPA实施模式中,我们会重新提到这一问题。
我希望这能够说服你,在使用JPA时,确实需要DAO。J
第一个JPA的实施模式到此就介绍完了,在该系列接下来的博客文章中,我们将以这一例子为基础来讨论下一个模式,在此期间我将非常乐意倾听你是如何编写自己的DAO的!