在上一篇文章我們曾說過,DAO 模式主要按下面的思路來實現:
1.系統中的所有數據庫訪問都通過 DAO 進行以實現封裝。
2. 每個 DAO 實例負責一個主要域對象或實體。
3.DAO 負責域對象的創建、讀取(按主鍵)、更新和刪除(CRUD)。
4. DAO 可允許基於除主鍵之外的標準進行查詢,返回值通常是DAO 負責的域對象集合。
5.爲了實現靈活性,DAO 不負責處理事務、會話或連接,而把這交給一個工具類。
下面進行具體介紹
一 統一 DAO的創建
在實際的應用中,小茵茵使用的是Hibernate進行數據庫的操作。在最開始的開發過程中,我給每一個DAO都創建了增,刪,改,查的方法。但 是寫到一半的時候,小茵茵就發現,其實每個DAO的操作都是類似的,於是我提取出了一個CommonDAO用於進行最基礎的操作,這樣不但讓編寫每個 DAO的工作變得簡單了, 而且進行改動的時候也可以集中到一處。下面是CommonDAO的代碼:
import java.util.Iterator;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
/**
* The abstract class of all the DAO all the methods are wrapped in transaction
*
* @author yiqian
*
*/
public abstract class CommonDAO {
/**
* insert a object into DB
*
* @param obj
* @throws Exception
*/
protected void insert(Object obj) throws Exception {
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
session.save(obj);
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
/**
* insert a list of the object in a transaction
*
* @param list
* a list of the objects
*
* @throws Exception
*/
public void insert(List list) throws Exception {
if (list == null)
throw new Exception(
"CommonDAO Error: insert a null list through DAO");
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
Iterator iter = list.iterator();
while (iter.hasNext())
session.save(iter.next());
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
/**
*
* @param id
* primary key
* @param myClass
* class of the Object
* @return the object if the id is valid
* @throws Exception
*/
protected Object load(Class myClass, long id) throws Exception {
Object obj = null;
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
obj = session.get(myClass, id);// use get instead of load
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
return obj;
}
/**
* delete a object from DB
*
* @param obj
* @throws Exception
*/
protected void delete(Object obj) throws Exception {
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
session.delete(obj);
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
/**
* delete a list of objects in a transaction
*
* @param list
* @throws Exception
*/
public void delete(List list) throws Exception {
if (list == null)
throw new Exception(
"CommonDAO Error: delete a null list through DAO");
Iterator iter = list.iterator();
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
while (iter.hasNext())
session.delete(iter.next());
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
/**
*
* @param id
* @throws Exception
*/
protected void delete(Class myClass, long id) throws Exception {
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
Object obj = session.get(myClass, id);
if (obj == null)
throw new Exception(myClass + ": delete with an invalid id");
session.delete(obj);
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
/**
*
* @param myClass
* @return all the data in corresponding table
* @throws Exception
*/
protected List findAll(Class myClass) throws Exception {
List list = null;
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
Criteria criteria = session.createCriteria(myClass);
list = criteria.list();
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
return list;
}
/**
* find all the rows according to the restricitions
*
* @param myClass
* @param cris
* list of restrictions
* @param order
* result set order
* @return
* @throws Exception
*/
protected List findAll(Class myClass, List<Criterion> cris, Order order)
throws Exception {
List list = null;
Session session = HibernateUtil.getCurrentSession();
Criteria criteria = session.createCriteria(myClass);
// add the criterions
if (cris != null) {
Iterator<Criterion> iter = cris.iterator();
while (iter.hasNext())
criteria.add(iter.next());
}
// add the order
if (order != null)
criteria.addOrder(order);
// begin the transaction
HibernateUtil.beginTransaction();
list = criteria.list();
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
return list;
}
/**
* find all the rows according to the restricitions
*
* @param myClass
* @param cris
* restrictions
* @return
* @throws Exception
*/
protected List findAll(Class myClass, List<Criterion> cris)
throws Exception {
List list = null;
Session session = HibernateUtil.getCurrentSession();
Criteria criteria = session.createCriteria(myClass);
// add the criterions
if (cris != null) {
Iterator<Criterion> iter = cris.iterator();
while (iter.hasNext())
criteria.add(iter.next());
}
// begin the transaction
HibernateUtil.beginTransaction();
list = criteria.list();
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
return list;
}
/**
*
* @param src -
* it should be an object returned by load method
* @throws Exception
*/
protected void update(Object obj) throws Exception {
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
session.update(obj);// make it bind with the session cache
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
/**
* update a list of objects in a transaction
*
* @param src
* a list of objects loaded through DAO
* @throws Exception
*/
public void update(List list) throws Exception {
if (list == null)
throw new Exception(
"CommonDAO Error: update a null list through DAO");
Iterator iter = list.iterator();
Session session = HibernateUtil.getCurrentSession();
HibernateUtil.beginTransaction();
while (iter.hasNext())
session.update(iter.next());// make it bind with the session cache
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
}
二 創建一個特定的DAO
有了CommonDAO之後,我們就可以輕鬆的編寫一個特定的DAO了。假定數據庫中有一張TestAgent表,它的建表語句是:
DROP TABLE IF EXISTS `dev_autotest_user`.`at_testagent_ta`;
CREATE TABLE `dev_autotest_user`.`at_testagent_ta` (
`id_ta` bigint(20) unsigned NOT NULL auto_increment,
`is_available_ta` tinyint(4) NOT NULL,
`consumer_ta` bigint(20) default NULL,
`type_ta` tinyint(4) NOT NULL,
`agent_info_ta` varchar(1024) NOT NULL,
UNIQUE KEY `id_ta` (`id_ta`)
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8;
對應的持久化類TestAgent.java爲:
public class TestAgent {
private long id;
private short is_available;
private long consumer;
private short type;
private String agent_info;
public TestAgent() {
id = -1;
}
public long getConsumer() {
return consumer;
}
public void setConsumer(long consumer) {
this.consumer = consumer;
}
public long getId() {
return id;
}
private void setId(long id) {
this.id = id;
}
public short getIs_available() {
return is_available;
}
public void setIs_available(short is_available) {
this.is_available = is_available;
}
public short getType() {
return type;
}
public void setType(short type) {
this.type = type;
}
public String getAgent_info() {
return agent_info;
}
public void setAgent_info(String agent_info) {
this.agent_info = agent_info;
}
public String toString()
{
return "id:"+id+",isAvailable:"+this.is_available+",consumer:"+this.consumer+",type:"+this.type;
}
那麼Hibernate配置文件就是TestAgent.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.ebay.autotest.data.po.TestAgent"
table="at_testagent_ta">
<id name="id" type="long" length="20">
<column name="id_ta" not-null="true" />
<generator class="increment" />
</id>
<property name="is_available" type="short" length="4">
<column name="is_available_ta" not-null="true" />
</property>
<property name="consumer" type="long" length="20">
<column name="consumer_ta" not-null="false" />
</property>
<property name="type" type="short" length="4">
<column name="type_ta" not-null="true" />
</property>
<property name="agent_info" type="string" length="1024">
<column name="agent_info_ta" not-null="true" />
</property>
</class>
</hibernate-mapping>
現在,我們創建TestAgentDAO:
public class TestAgentDAO extends CommonDAO {
public TestAgentDAO() {
// TODO Auto-generated constructor stub
}
/**
*
* @param agent
* @return if the operation is successful
* @throws Exception
*/
public void insert(TestAgent agent) throws Exception {
super.insert(agent);
}
/**
*
* @param id
* primary key
* @return TestAgent if the id is valid
* @throws Exception
*/
public TestAgent load(long id) throws Exception {
return (TestAgent) super.load(TestAgent.class, id);
}
/**
*
* @param agent ,
* it should be an agent loaded through DAO
* @return
* @throws Exception
*/
public void delete(TestAgent agent) throws Exception {
super.delete(agent);
}
public void delete(long id) throws Exception {
super.delete(TestAgent.class, id);
}
public List<TestAgent> findAll() throws Exception {
return (List<TestAgent>) super.findAll(TestAgent.class);
}
/**
*
* @param available
* @return
* @throws Exception
*/
public List<TestAgent> findAll(short available) throws Exception {
Criterion cri = Expression.eq("is_available", available);
List<Criterion> list = new ArrayList<Criterion>();
list.add(cri);
return (List<TestAgent>) super.findAll(TestAgent.class, list);
}
/**
*
* @param src -
* it should be an object returned by load method
* @throws Exception
*/
public void update(TestAgent src) throws Exception {
super.update(src);
}
}
三 關於HibernateUtil
上面已經提到了,可以把對於session和transaction的操作都集中到HibernateUtil這個類中去。這裏要感謝http://blog.csdn.net/lin_bei/archive/2006/08/10/1048051.aspx這篇文章的作者,他提供了一個非常強大的util類,讓我學到了不少:)
下面是代碼:
mport javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Interceptor;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
/**
* make sure every thread get its own session
*
* @author yiqian
*
*/
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() throws Exception {
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() throws Exception {
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() throws Exception {
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();
}
}
大家可以從lin_bei的文章中得到這個類的詳細說明,在這裏我就不多說了。
總結:
AutoTest這個項目是我第一次寫正式的DAO和第一次使用Hibernate,現在phase I的開發工作馬上就要完成了,在這裏我要給出我的一些小建議:
1 儘早的建立junitTest
大家可能發現了,小茵茵在開發過程中已經refactor了一次,而且由於這個項目被分成了若干個component,所以整個project的配置也經常發生變化,很容易會出現很多問題。但是要感謝junit,在每update項目或者commit我自己的代碼之前,我都會跑一次junit Tests,這樣能夠很容易發現問題。而且在後期的集成工作中,當其他component調用我的接口出現問題時,我可以用junit很簡單的檢測出是否是我的代碼內部有問題還是他們調用的時候出現了差錯
另一個好處就是junit可以個別的開發人員一個使用的範例,大家照着我的junit case寫他們的logic就避免了很多問題
2 合理的使用抽象
CommonDAO的建立使我省去了不少開發時間,而且使之後的維護工作變的更加順利。現在事務的封裝都是在CommonDAO完成,也許今後我會開放事務操作的接口,讓business logic來進行事務的管理,這樣就能靈活的保證多個操作在同一事務中。