作為JavaEE5 規(guī)范一部分的JPA,已經(jīng)有眾多的實(shí)現(xiàn),比如:Hiberante,TopLink, EclipseLink和OpenJPA 等許多ORM框架。JPA不僅僅應(yīng)用于EJB3中,你也可以應(yīng)用EJB3之外的應(yīng)用,比如Spring 中。甚至Gavin King Hiberante的作者 在Hiberante in Action 的第二版書中也推薦使用JPA。很明顯,JPA將被廣泛的應(yīng)用。
一旦你熟悉annotation,并嘗試過Hello world之類的程序后,你會(huì)感覺,恩 JPA確實(shí)不錯(cuò)。然后,當(dāng)你在你的項(xiàng)目中開始應(yīng)用JPA時(shí),你會(huì)發(fā)現(xiàn)其實(shí)并不那么簡(jiǎn)單,你需要考慮如何處理事務(wù),惰性加載,移除對(duì)象實(shí)例, 繼承等等。這些問題會(huì)在本系列中給你答案。
首先進(jìn)入第一的設(shè)計(jì)模式 DAO。
我們需要DAO嗎?
在二年前,我們已經(jīng)討論了這個(gè)問題了,討論的結(jié)果是:是否使用DAO依賴于你的應(yīng)用,類似于GOF的設(shè)計(jì)模式,如果是很簡(jiǎn)單的應(yīng)用程序,應(yīng)用設(shè)計(jì)模式,只能增加你的復(fù)雜度。 而對(duì)于復(fù)雜應(yīng)用程序,正因?yàn)槠鋸?fù)雜,應(yīng)用設(shè)計(jì)模式可以減低復(fù)雜度,提高可維護(hù)性。
應(yīng)用了DAO我們可以得到如下好處:
1. 避免在任何存取數(shù)據(jù)的代碼中引入EntityManager,減少了依賴。
2. 對(duì)某些實(shí)體Bean操作增加限制。比如你不想對(duì)LogEntry提供刪除操作,應(yīng)用DAO,你僅僅做的是不要在LogEntry DAO中不要添加remove的方法。
3. 理論上,應(yīng)用DAO層,你一個(gè)自由切換其他的持久機(jī)制比如純JDBC或者Ibatis.而實(shí)際上,JPA已經(jīng)是一個(gè)抽象層,這種切 換是沒有意義的。
4. 你可以在一個(gè)實(shí)體中集中所有的查詢避免這些查詢?cè)谄渌a中出現(xiàn)。你可以使用named queries 在實(shí)體類中查詢,但是您仍然需要正確的參數(shù)被設(shè)置。對(duì)應(yīng)這種查詢,你可以在DAO中設(shè)置參數(shù),然后轉(zhuǎn)換成正確的返回類型,比如:
public List<ChangePlan> findExecutingChangePlans() {
Query query = entityManager.createQuery(
"SELECT plan FROM ChangePlan plan where plan.state = 'EXECUTING'");
return (List<ChangePlan>) query.getResultList();
}
然后,但你決定使用DAO時(shí),如何正確合理的使用它呢? 在Spring的JpaTemplate的JavaDoc中建議,不要使用類似特殊的類,類似JpaDaoSupport。 而是你應(yīng)該自己通過@PersistenceContext注釋獲取EntityManger來維護(hù)你的DAO。這種方式可以在EJB3的容器中工作,并且如果你在Spring的context 加入PersistenceAnnotationBeanpostProcessor Bean的話,在Spring2.0里也是沒有問題的。
類型安全且泛化的DAO pattern
因?yàn)槊恳粋€(gè)DAO都包含一些相同的邏輯,所以我們應(yīng)該抽取這些邏輯放入到父類中。
實(shí)體類
比如,我們想實(shí)例化OrderClass:
@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;}
DAO 接口
首先我們定義一個(gè)泛化的DAO接口,包括最常用的方法,比如:
public interface Dao<K, E> {
void persist(E entity);
void remove(E entity);
E findById(K id);
}
K ,E為泛型,你可以加其他的方法比如:List findAll().
然后我們定義一個(gè)自接口,實(shí)現(xiàn)特殊的方法,比如,我們想查找一定條件的Order信息:
public interface OrderDao extends Dao<Integer, Order> {
List<Order> findOrdersSubmittedSince(Date date);
}
基本DAO的實(shí)現(xiàn)
第三步就是創(chuàng)建基本的JPA DAO的實(shí)現(xiàn)。它實(shí)現(xiàn)了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); }
上述實(shí)現(xiàn)是直接明了的,大家還要注意幾點(diǎn):
JpaDao的構(gòu)造方法是通過ParameterizedType獲取具體的實(shí)體類。
通過@PersistenceContext 獲取EntityManager
entityClass和EntityManager是Protected類型,子類可以直接訪問。
特殊的DAO實(shí)現(xiàn)
最后,我們創(chuàng)建特殊的DAO,它擴(kuò)張了基本的DAO接口實(shí)現(xiàn)了OrderDAO接口
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的使用
在EJB3中,
@EJB(name="orderDao")
private OrderDao orderDao;
在Spring中,我們使用XML bean 文件,或者通過autowiring 如下:
@Autowired
public OrderDao orderDao;
總之。當(dāng)我們獲取到了DAO的引用后,我們可以如下使用:
Order o = new Order();
o.setCustomerName("Peter Johnson");
o.setDate(new Date());
orderDao.persist(o);
當(dāng)然,我們可以再OrderDao接口中加入其它的查詢:
List<Order> orders = orderDao.findOrdersSubmittedSince(date);
for (Order each : orders) {
System.out.println("order id = " + each.getId());
}
總之,使用了類型安全的DAO Pattern有如下優(yōu)勢(shì):
1. 從Client的角度講,不會(huì)直接依賴于JPA的API
2. 通過泛型實(shí)現(xiàn)類型安全,避免Cast異常。
3. 集中處理所有跟JPA相關(guān)的代碼。
4. 可以在集中點(diǎn)上加入事務(wù),log 性能測(cè)試等邏輯。
5. 通過一個(gè)類可以測(cè)試數(shù)據(jù)庫(kù)訪問代碼。
希望這些可以說服你使用DAO Pattern . 接下來我們將討論Bidirectional assocations 雙向關(guān)聯(lián)模式。