20 October 2005

Hibernate 3 新的範例程式 CaveatEmptor,開始導入了 generic,並且設計了一個 Generic DAO。這裡設計的 DAO 較為"泛用",但使用上也較為複雜 ,而且還包含了一個 DAOFactory。如果專案很大的話,又沒有 Spring 時,不妨套用他的 DAOFactory。我自個則是作了一些簡化,下面是我的 GenericDAO:

public abstract class GenericHibernateDAO<T> {

    protected HibernateTemplate hibernateTemplate;

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    @SuppressWarnings("unchecked")
    public T findById(final Long id) {
        if (id == null) {
            throw new NullPointerException("id can't be null.");
        }

        return (T) hibernateTemplate.execute(new HibernateCallback() {
            public Object doInHibernate(Session session)
                    throws HibernateException, SQLException {
                final String hql = " from " + getPersistentObjectName()
                        + " as x where x.id = :id ";
                return session.createQuery(hql).setLong("id", id.longValue())
                        .uniqueResult();
            }
        });
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        return hibernateTemplate.find(" from " + getPersistentObjectName());
    }

    private String getPersistentObjectName() {
        return getPersistentClass().getSimpleName();
    }

    public void save(T object) {
        if (object == null) {
            throw new NullPointerException("can not save or update null object");
        }
        hibernateTemplate.saveOrUpdate(object);
    }

    public void delete(T object) {
        if (object == null) {
            throw new NullPointerException("can not delete null object");
        }
        hibernateTemplate.delete(object);
    }

    //subclass to provide actual Persistence Class
    protected abstract Class<T> getPersistentClass();

    //方便的 method
    protected Object uniqueResult(List queriedList) {
        if (queriedList == null || queriedList.isEmpty()) {
            return null;
        }
        if (queriedList.size() > 1) {
            throw new IllegalStateException("not unique result: total "
                    + queriedList.size());
        }
        return queriedList.get(0);
    }

    //方便的 method
    @SuppressWarnings("unchecked")
    protected List distinct(List queriedList) {
        if (queriedList.size() <= 1) {
            return queriedList;
        }
        return new ArrayList(new LinkedHashSet(queriedList));
    }


}

提供 findById() findAll() save() delete() 等四個最基本的操作。使用上,這個 GenericHibernateDAO 需要 inject HibernateTemplate。然後 subclass:

public class BookDAO extends GenericHibernateDAO<Book> {

    @Override
    protected Class<Book> getPersistentClass() {
        return Book.class;
    }
}

subclass 需要宣告成 extends GenericHibernateDAO<Book>,並且 override getPersistentClass() 回傳 Book.class。就這麼簡單,一個 DAO 就搞定了。如果一個專案有 10 個 DAO,那麼便可省下 40 個 method,40 個 testCase (DAO 的 test case 通常還跑的特別慢... 能越少當然越好囉)。

當然這個 GenericHibernateDAO 限制很多,(1) 只限用在 pk 為 Long 的 persistent object (2) 動態產生 HQL 來 query findById() 和 findAll()。第一點的話是因為我們專案強制統一 pk 要用 Long,所以這個限制對我們來說沒什麼大礙 (個人也建議大家使用這個 practice)。如果你需要用其他的 pk 那可以參考上面連結裡所提到的正統 GenericDAO 的做法。第二點是 動態產生 HQL,正統的作法是用 Criteria 的方式啦... 不過我對這種做法很感冒,因為它很容易就 join 不相關的資料出來,我通常比較希望 findByXXX() 只 query 需要的物件出來,而其他的關聯物件則都是 lazy。Query by HQL 正是有這種特性。如果真有需要做特殊的 join fetch,在 subclass override 改一改就好了。

後面還加了兩個常用的 method: uniqueResult 和 distinct,這兩個都是 hibernateTemplate 欠缺的 method,放在這裡可以讓所有的 subclass DAO 使用。

jdk5 的 Generic 除了 Collection 之外,現在又多了一個很棒的 DAO 應用。如果你們的專案已經導入 jdk5,那麼可以開始嘗試設計自己的 generic DAO。讓程式碼更為精簡!

最後,提醒一下,雖然有了 Generic DAO,但不代表你可以任意的增加你的 DAO (即每一個 Table 都配一個 DAO) DAO 通常只有獨立的 Entity 和 Aggregate 才適用,切記切記。

Update: getPersistentClass() 可以用 reflection 取得,所以 subclass 不用再 override 了:

public abstract class GenericHibernateDAO<T> {
    private final Class<T> persistentClass;

    public GenericHibernateDAO() {
        this.persistentClass = (Class<T>) findParameterizedType(getClass())
                .getActualTypeArguments()[0];
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    private ParameterizedType findParameterizedType(Class clazz) {
        Type genericSuperclass = clazz.getGenericSuperclass();
        if (!(genericSuperclass instanceof ParameterizedType)) {
            return findParameterizedType(clazz.getSuperclass());
        }
        return (ParameterizedType) genericSuperclass;
    }
}

回響

可以用 Tag <I>、<B>,程式碼請用 <PRE>