一 .有關DAO模式的介紹
業務對象只應該關注業務邏輯,不應該關心數據存取的細節。數據訪問對象必須實現特定的持久化策略(如,基于JDBC或Hibernate的持久化邏輯), 這樣就抽出來了DAO層,作為數據源層,而之上的Domain Model層與之通訊而已,如果將那些實現了數據訪問操作的所有細節都放入高層Domain model(領域模型)的話,系統的結構一定層次上來說就變得有些混亂。低級別的數據訪問邏輯與高級別的業務邏輯分離,用一個DAO接口隱藏持久化操作的 細節,這樣使用的最終目的就是讓業務對象無需知道底層的持久化技術知識,這是標準 j2ee 設計模式之一。一個典型的的DAO組成:DAO工廠類,DAO接口,實現DAO接口的具體類(每個 DAO 實例負責一個主要域對象或實體),VO(Value Object)。如果一個DAO 工廠只為一個數據庫的實現(現在只考慮這種情況)而創建很多的DAO的時候,實現該策略時,我們考慮采用工廠方法設計模 式.
二.設計DAO要注意的問題
在采用這種工廠方法設計模式來實現時我們其實要注意很多問題,哪個對象負責開始事務,哪個負責事務結束?DAO 是否要負責事務的開始和結束? 應用程序是否需要通過多少個DAO訪問數據?事務涉及一個DAO還是多個DAO?一個DAO是否調用另一個DAO的方法?了解上述問題的答案將有助于我們 選擇最適合的 DAO 的事務界定策略。在 DAO 中有兩種主要的界定事務的策略。一種方式是讓 DAO 負責界定事務,另一種將事務界定交給調用這個 DAO 方法的對象處理。如果選擇了前一種方式,那么就將事務代碼嵌入到 DAO 中。如果選擇后一種方式,那么事務界定代碼就是在 DAO 類外面,在這里我將用<<Hibernate項目開發寶典>>中留言版的小例子來理解后一種工作方式是如何工作的,以及如何自己 實現一個類似Spring的IOC輕量級容器中Bean工廠的功能(當然是沒有使用Spring應用程序框架的情況下,對于這個簡單的例子來說更有助于我 們理解Spring的DI模式)。這個小實例所要實現的業務功能包括創建用戶,用戶登錄,發表文章,瀏覽文章,修改文章和刪除文章,所以有兩個對應的實體 對象User,Message。本文不涉及到業務邏輯,以及顯示層部分。
三.DAO的實現
DAO 模式對開發J2EE應用的人員來說都應該很熟悉的,但是模式的實現各不相同,在這里我將按下面的思路來實現:
1.系統中的所有數據庫訪問都通過 DAO 進行以實現封裝。
2. 每個 DAO 實例負責一個主要域對象或實體。
3.DAO 負責域對象的創建、讀取(按主鍵)、更新和刪除(CRUD)。
4. DAO 可允許基于除主鍵之外的標準進行查詢,返回值通常是DAO 負責的域對象集合。
5.像上面說的,DAO 不負責處理事務、會話或連接,而把這交給一個工具類,這樣做是為了實現靈活性。
(一)泛型 DAO 接口
泛型 DAO 的基礎是其 CRUD 操作。下面的接口定義泛型 DAO 的方法:
提供數據庫操作接口給業務層使用
清單1
public interface IMessageDAO
{ //對應留言信息Message這個實體對象的操作
public void saveMessage( Message message );
public void updateMessage( Message message );
public List getMessages( );
public void deleteMessage( String id, String userId );
public Message getMessage( String id );
}
清單2
public interface IUserDAO
{
public void saveUser( User user );
public User getUser( String username );
public User getUserById( String id );
}
(二)泛型DAO的實現
第一個泛型 DAO 的實現
DAO 的實現類,封裝數據庫邏輯,按<<Hibernate項目開發寶典>>書上所說的將那些持久化操作封裝到一個DAO基礎類,也相 當于是一個工具類,通過繼承這個基礎類,DAO的實現類可以在很大程度上簡化持久化操作的步驟,減少代碼的重復量。這個基礎類命名為 HibernateDAO,具體的方法實現如清單2
清單 3.
/**
* 使用Hibernate實現DAO的基礎類
* 包括了持久化操作的一些基礎方法
*/
public class HibernateDAO
{
/**
* 保存對象信息到數據庫
* @param obj 需要進行持久化操作的對象
*/
public void saveObject(Object obj)
{
HibernateUtil.getCurrentSession().save(obj);
}
/**
* 更新持久化對象
* @param obj 需要更新的對象
*/
public void updateObject(Object obj)
{
HibernateUtil.getCurrentSession().update(obj);
}
/**
* 使用HQL語句進行查詢
* @param hsql 查詢語句
* @return 符合條件的對象集合
*/
public List getObjects(String hsql)
{
List result = HibernateUtil.getCurrentSession().createQuery(hsql).list();
return result;
}
/**
* 使用HQL語句進行對象的查詢
* @param hsql 查詢語句
* @return 符合條件的對象
*/
public Object getObject(String hsql)
{
Object result = HibernateUtil.getCurrentSession().createQuery(hsql).uniqueResult();
return result;
}
/**
* 根據ID值得到持久化的對象
* @param cls 對象的類型
* @param id ID值
* @return 指定ID的對象
*/
public Object getObject(Class cls, String id)
{
Object result = HibernateUtil.getCurrentSession().get(cls, id);
return result;
}
/**
* 刪除對象信息
* @param obj 被刪除的對象
*/
public void deleteObject(Object obj)
{
HibernateUtil.getCurrentSession().delete(obj);
}
}
清單 4. IMessageDAO接口的實現類
/**
* IMessageDAO接口的Hibernate實現
*/
public class MessageDAO extends HibernateDAO implements IMessageDAO
{
/**
* 保存留言信息
*
* @param message
* 被保存的留言對象
*/
public void saveMessage(Message message)
{
super.saveObject(message);
}
/**
* 得到所有的留言信息
*
* @return 返回所有的留言信息
*/
public List getMessages()
{
String hsql = "from Message";
return super.getObjects(hsql);
}
/**
* 刪除留言信息
*
* @param id
* 要刪除的留言信息的ID值
* @param userId
* 執行刪除操作的用戶ID
*/
public void deleteMessage(String id, String userId)
{
Message msg = getMessage(id);
if (msg == null)
{
throw new MessageDAOException("找不到你所要刪除的留言!");
}
if (!msg.getUser().getId().equals(userId))
{
throw new MessageDAOException("你不能刪除別人的留言!");
}
deleteObject(msg);
}
/**
* 得到留言信息
*
* @param id
* 留言的ID值
* @return 指定ID值得留言對象
*/
public Message getMessage(String id)
{
return (Message) getObject(Message.class, id);
}
/**
* 更新留言信息
*
* @param message
* 欲更新的留言對象
*/
public void updateMessage(Message message)
{
updateObject(message);
}
}
清單 5. IUserDAO接口的實現類
/**
* IUserDAO接口的Hibernate實現
*/
public class UserDAO extends HibernateDAO implements IUserDAO
{
/**
* 保存用戶信息到數據庫
* @param user 被保存的用戶對象
*/
public void saveUser(User user)
{
if (user == null)
return;
User u = getUser(user.getName());
if (u != null)
throw new MessageDAOException("用戶名已經存在,請使用其它用戶名!");
saveObject(user);
}
/**
* 得到用戶對象
* @param username 用戶的登錄名
* @return 指定登錄名的用戶對象
*/
public User getUser(String username)
{
User u = (User) getObject("from User u where u.name = '" + username
+ "'");
return u;
}
/**
* 得到用戶對象的信息
* @param id 用戶的ID值
* @return 指定的用戶信息
*/
public User getUserById(String id)
{
return (User) getObject(User.class, id);
}
}
四.事務界定
前面說過, DAO 不負責處理事務、會話或連接,而把這交給一個工具類,封裝所有關于數據庫的操作。把Session的獲取,語句的關閉等放在這個類更好。通常的設計把數據 庫的代碼放到DAO的實現類中,這樣如果某個DAO實現類設計不良,要改動就必須牽涉到很多地方,不利于維護。在這里的工具類代碼如清單6。
清單 6.
public class HibernateUtil
{
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final String INTERCEPTOR_CLASS = "hibernate.util.interceptor_class";
private static Configuration configuration;
private static SessionFactory sessionFactory;
private static ThreadLocal threadSession = new ThreadLocal();
private static ThreadLocal threadTransaction = new ThreadLocal();
private static boolean useThreadLocal = true;
static {
// Create the initial SessionFactory from the default configuration files
try {
// Replace with Configuration() if you don't use annotations or JDK 5.0
//configuration = new AnnotationConfiguration();
configuration = new Configuration();
// Read not only hibernate.properties, but also hibernate.cfg.xml
configuration.configure();
// Assign a global, user-defined interceptor with no-arg constructor
String interceptorName = configuration.getProperty(INTERCEPTOR_CLASS);
if (interceptorName != null) {
Class interceptorClass =
HibernateUtil.class.getClassLoader().loadClass(interceptorName);
Interceptor interceptor = (Interceptor)interceptorClass.newInstance();
configuration.setInterceptor(interceptor);
}
// Disable ThreadLocal Session/Transaction handling if CMT is used
if (org.hibernate.transaction.CMTTransactionFactory.class.getName()
.equals( configuration.getProperty(Environment.TRANSACTION_STRATEGY) ) )
useThreadLocal = false;
if (configuration.getProperty(Environment.SESSION_FACTORY_NAME) != null) {
// Let Hibernate bind it to JNDI
configuration.buildSessionFactory();
} else {
// or use static variable handling
sessionFactory = configuration.buildSessionFactory();
}
} catch (Throwable ex) {
// We have to catch Throwable, otherwise we will miss
// NoClassDefFoundError and other subclasses of Error
log.error("Building SessionFactory failed.", ex);
throw new ExceptionInInitializerError(ex);
}
}
/**
* Returns the original Hibernate configuration.
*
* @return Configuration
*/
public static Configuration getConfiguration() {
return configuration;
}
/**
* Returns the global SessionFactory.
*
* @return SessionFactory
*/
public static SessionFactory getSessionFactory() {
SessionFactory sf = null;
String sfName = configuration.getProperty(Environment.SESSION_FACTORY_NAME);
if ( sfName != null) {
log.debug("Looking up SessionFactory in JNDI.");
try {
sf = (SessionFactory) new InitialContext().lookup(sfName);
} catch (NamingException ex) {
throw new RuntimeException(ex);
}
} else {
sf = sessionFactory;
}
if (sf == null)
throw new IllegalStateException("SessionFactory not available.");
return sf;
}
/**
* Closes the current SessionFactory and releases all resources.
* <p>
* The only other method that can be called on HibernateUtil
* after this one is rebuildSessionFactory(Configuration).
*/
public static void shutdown() {
log.debug("Shutting down Hibernate.");
// Close caches and connection pools
getSessionFactory().close();
// Clear static variables
configuration = null;
sessionFactory = null;
// Clear ThreadLocal variables
threadSession.set(null);
threadTransaction.set(null);
}
/**
* Rebuild the SessionFactory with the static Configuration.
* <p>
* This method also closes the old SessionFactory before, if still open.
* Note that this method should only be used with static SessionFactory
* management, not with JNDI or any other external registry.
*/
public static void rebuildSessionFactory() {
log.debug("Using current Configuration for rebuild.");
rebuildSessionFactory(configuration);
}
/**
* Rebuild the SessionFactory with the given Hibernate Configuration.
* <p>
* HibernateUtil does not configure() the given Configuration object,
* it directly calls buildSessionFactory(). This method also closes
* the old SessionFactory before, if still open.
*
* @param cfg
*/
public static void rebuildSessionFactory(Configuration cfg) {
log.debug("Rebuilding the SessionFactory from given Configuration.");
synchronized(sessionFactory) {
if (sessionFactory != null && !sessionFactory.isClosed())
sessionFactory.close();
if (cfg.getProperty(Environment.SESSION_FACTORY_NAME) != null)
cfg.buildSessionFactory();
else
sessionFactory = cfg.buildSessionFactory();
configuration = cfg;
}
}
/**
* Retrieves the current Session local to the thread.
* <p/>
* If no Session is open, opens a new Session for the running thread.
* If CMT is used, returns the Session bound to the current JTA
* container transaction. Most other operations on this class will
* then be no-ops or not supported, the container handles Session
* and Transaction boundaries, ThreadLocals are not used.
*
* @return Session
*/
public static Session getCurrentSession() {
if (useThreadLocal) {
Session s = (Session) threadSession.get();
if (s == null) {
log.debug("Opening new Session for this thread.");
s = getSessionFactory().openSession();
threadSession.set(s);
}
return s;
} else {
return getSessionFactory().getCurrentSession();
}
}
/**
* Closes the Session local to the thread.
* <p>
* Is a no-op (with warning) if called in a CMT environment. Should be
* used in non-managed environments with resource local transactions, or
* with EJBs and bean-managed transactions.
*/
public static void closeSession() {
if (useThreadLocal) {
Session s = (Session) threadSession.get();
threadSession.set(null);
Transaction tx = (Transaction) threadTransaction.get();
if (tx != null && (!tx.wasCommitted() || !tx.wasRolledBack()) )
throw new IllegalStateException("Closing Session but Transaction still open!");
if (s != null && s.isOpen()) {
log.debug("Closing Session of this thread.");
s.close();
}
} else {
log.warn("Using CMT/JTA, intercepted superfluous close call.");
}
}
/**
* Start a new database transaction.
* <p>
* Is a no-op (with warning) if called in a CMT environment. Should be
* used in non-managed environments with resource local transactions, or
* with EJBs and bean-managed transactions. In both cases, it will either
* start a new transaction or join the existing ThreadLocal or JTA
* transaction.
*/
public static void beginTransaction() {
if (useThreadLocal) {
Transaction tx = (Transaction) threadTransaction.get();
if (tx == null) {
log.debug("Starting new database transaction in this thread.");
tx = getCurrentSession().beginTransaction();
threadTransaction.set(tx);
}
} else {
log.warn("Using CMT/JTA, intercepted superfluous tx begin call.");
}
}
/**
* Commit the database transaction.
* <p>
* Is a no-op (with warning) if called in a CMT environment. Should be
* used in non-managed environments with resource local transactions, or
* with EJBs and bean-managed transactions. It will commit the
* ThreadLocal or BMT/JTA transaction.
*/
public static void commitTransaction() {
if (useThreadLocal) {
Transaction tx = (Transaction) threadTransaction.get();
try {
if ( tx != null && !tx.wasCommitted()
&& !tx.wasRolledBack() ) {
log.debug("Committing database transaction of this thread.");
tx.commit();
}
threadTransaction.set(null);
} catch (RuntimeException ex) {
log.error(ex);
rollbackTransaction();
throw ex;
}
} else {
log.warn("Using CMT/JTA, intercepted superfluous tx commit call.");
}
}
/**
* Rollback the database transaction.
* <p>
* Is a no-op (with warning) if called in a CMT environment. Should be
* used in non-managed environments with resource local transactions, or
* with EJBs and bean-managed transactions. It will rollback the
* resource local or BMT/JTA transaction.
*/
public static void rollbackTransaction() {
if (useThreadLocal) {
Transaction tx = (Transaction) threadTransaction.get();
try {
threadTransaction.set(null);
if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() ) {
log.debug("Tyring to rollback database transaction of this thread.");
tx.rollback();
log.debug("Database transaction rolled back.");
}
} catch (RuntimeException ex) {
throw new RuntimeException("Might swallow original cause, check ERROR log!", ex);
} finally {
closeSession();
}
} else {
log.warn("Using CMT/JTA, intercepted superfluous tx rollback call.");
}
}
/**
* Reconnects a Hibernate Session to the current Thread.
* <p>
* Unsupported in a CMT environment.
*
* @param session The Hibernate Session to be reconnected.
*/
public static void reconnect(Session session) {
if (useThreadLocal) {
log.debug("Reconnecting Session to this thread.");
session.reconnect();
threadSession.set(session);
} else {
log.error("Using CMT/JTA, intercepted not supported reconnect call.");
}
}
/**
* Disconnect and return Session from current Thread.
*
* @return Session the disconnected Session
*/
public static Session disconnectSession() {
if (useThreadLocal) {
Transaction tx = (Transaction) threadTransaction.get();
if (tx != null && (!tx.wasCommitted() || !tx.wasRolledBack()) )
throw new IllegalStateException("Disconnecting Session but Transaction still open!");
Session session = getCurrentSession();
threadSession.set(null);
if (session.isConnected() && session.isOpen()) {
log.debug("Disconnecting Session from this thread.");
session.disconnect();
}
return session;
} else {
log.error("Using CMT/JTA, intercepted not supported disconnect call.");
return null;
}
}
/**
* Register a Hibernate interceptor with the current SessionFactory.
* <p>
* Every Session opened is opened with this interceptor after
* registration. Has no effect if the current Session of the
* thread is already open, effective on next close()/getCurrentSession().
* <p>
* Attention: This method effectively restarts Hibernate. If you
* need an interceptor active on static startup of HibernateUtil, set
* the <tt>hibernateutil.interceptor</tt> system property to its
* fully qualified class name.
*/
public static void registerInterceptorAndRebuild(Interceptor interceptor) {
log.debug("Setting new global Hibernate interceptor and restarting.");
configuration.setInterceptor(interceptor);
rebuildSessionFactory();
}
public static Interceptor getInterceptor() {
return configuration.getInterceptor();
}
}
上 面的代碼中,如果是使用Hibernate3.1以上版本對Session的管理進行了優化,提供了內建的Session管理方式,所以上面也可以不用 ThreadLocal類型的實例對象來保存。書中提到一點,現在絕大多數的應用都是基于Web來實現的,這里通過Web所提供的Filter機制實現持 久化操作的進一步的封裝,將一個用戶請求中所做的所有持久化操作看成一個事務,當然,如果某個業務確實需要將這個請求分解成多個事務,那么也可以在業務實 現的方法中自行地進行事務的提交或者回流操作,完成的Hibernate如清單7