作者:Sunil Patil
译者:rotter_pal
版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Sunil Patil;
rotter_pal原文地址:
http://www.onjava.com/pub/a/onjava/2005/08/10/ibatisdao.html中文地址:
http://www.matrix.org.cn/resource/article/44/44058_iBatis+DAO.html关键词: iBatis DAO
在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象封装在一个公共API中。用程序设计的语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口在逻辑上对应这个特定的数据存储。
比如考虑在iBatis: SQL Maps中的应用例子。这是一个Struts应用允许对一个关系表执行SELECT, INSERT, UPDATE和DELETE的SQL请求。在这个应用中,使用SQL Maps做持续性框架。现在我们要修改这个应用,将这个关系表储存在一个XML文件中而不是存在关系数据库中,或者使用Hibernate来实现SELECT请求,而用SQL Map来执行其他请求,因为Hibernate提供了对高速缓存更好的支持。这样的修改很难实现,或者即使我们能修改而实现了这个功能,也会是很混乱的解决方案。
对于这类问题更好的解决方法是建立一个ContactDAO接口,在这个接口中定义处理SELECT, INSERT, UPDATE, 和DELETE 请求的事务方法。然后根据不同的事务逻辑建立不同的类实现各个方法。所以可能会有一个类处理使用SQL Maps同关系表进行交互的情况,而另外一个类处理用XML文件存放关系表而不是关系数据库的情况,等等。在项目中,根据实际的需要从不同的ContactDAO中选择相应的实现。这种关系见图1:
图1. ContactDAO 接口及实现
iBatis DAO是由Apache主持的开源框架项目,主要目标是为了解决这类问题。它允许在工程中以DAO模式为基础建立应用。这就意味着可以建立一个XML文件,并声明XMLContactDAO.java是ContactDAO的实现类,这个类知道如何从XML文件中读写数据。SQLMapContactDAO则知道如何用SQL Maps作为持续化框架与关系表进行交互。在工程中,如果向DAO框架提交一个需要XML的ContactDAO请求,框架则会返回一个XMLContactDAO对象。同样的DAO框架提供了唯一的接口处理事务管理,这个接口能实现与数据的存储方式无关。它同样考虑了底层连接管理细节和初始化存储框架。
这篇文章是关于如何一步一步的在项目中应用iBatis DAO框架的基础指导。我们将由如何把SQL Maps一文中的应用实例改为应用DAO框架入手。然后,我们要讨论DAO框架的构造。再下一步,我们关注事务管理是如何在DAO框架中得到支持的。最后一部分是关于如何建立自己的事务管理模块。
示例应用首先,我们将SQL Maps一文中的例子改为应用DAO框架。
1. 将ibatis-dao-2.jar文件复制到WEB-INF/lib目录下。
2. 在Java源程序的目录里新建一个如下的DAOMap.xml文件
清单1:<daoConfig>
<context id="sqlmap">
<transactionManager type="SQLMAP">
<property name="SqlMapConfigResource" value=
"com/sample/contact/dao/sqlmap/SqlMapConfig.xml"/>
</transactionManager>
<dao interface="com.sample.contact.dao.ContactDAO"
implementation=
"com.sample.contact.dao.sqlmap.SQLMapContactDAO"/>
</context>
</daoConfig>
DAOMap.xml是发布iBatis DAO框架的配置文件。<daoConfig>是根元素,每个<context>元素描述了一种存储机制。在这个例子中只使用了SQL Maps来存储,所以我们这里只有一个<context>元素。每种存储机制必须包含一个<transactionManager>元素,这个元素描述连接它后面的数据存储所用的管理器,并且标记事务的界限。我们将在稍后再讨论transactionManager。
<context>元素还包括一组DAO用于描述其他的存储管理机制。在这个例子中,我们将生成一个使用SQL Maps存储的ContactDAO,所以在配置文件中添加一个ie<dao>标记来定义SQLMapContactDAO。
3. 建立ContactDAO.java,如下:
清单2:public interface ContactDAO extends DAO {
public int insertContact(Contact contact);
public int updateContact(Contact contact);
public Contact selectContact(int contactId);
public int deleteContact(int contactId);
}
ContactDAO.java定义了用户和一个关系表进行交互所需要用到的所有事务处理方法。请注意到ContactDAO.java中的所有方法都将一个Contact对象作为参数,这是一个用来携带数据的数据传递对象。
4. 建立一个SQLMapContactDAO.java文件,如下
清单3:public class SQLMapContactDAO extends
SqlMapDaoTemplate implements ContactDAO {
public SQLMapContactDAO(DaoManager arg0) {
super(arg0);
}
public int deleteContact(int contactId) {
return super.delete("deleteContact",
new Integer(contactId));
}
public int insertContact(Contact contact) {
Integer contactId =(Integer)super.insert
("insertContact",contact);
return contact.getContactId();
}
public Contact selectContact(int contactId) {
return (Contact)super.queryForObject("getContact",
new Integer(contactId));
}
public int updateContact(Contact contact) {
return super.update("updateContact",contact);
}
}
SQLMapContactDAO是ContactDAO接口的具体实现,它用SQL Maps作为存储管理机制。注意到我们并没有写任何代码来或者初始化SQL Maps,或得到一个连接,或者在类中标注一个事务的界限。相反,我们继承SqlMapDaoTemplate.java类,它帮我们处理下层的、反复的操作。我们在SQLMapContactDAO类中需要考虑的唯一的事情就是事务处理逻辑。
5. 修改ContactSelectAction.java类中的execute()方法,如下:
清单4:Contact contactForm = (Contact) form;
Reader reader=
Resources.getResourceAsReader("DAOMap.xml");
DaoManager daoManager =
DaoManagerBuilder.buildDaoManager(reader);
ContactDAO contactDAO =
(ContactDAO) daoManager.getDao(
ContactDAO.class,"sqlmap");
request.setAttribute("contactDetail",
contactDAO.selectContact(
contactForm.getContactId()));
最后一步是修改ContactSelectAction类中的execute()方法,使它使用DAO框架。为了初始化DAO框架,我们需要一个为DAOMap.xml 准备一个Reader对象。iBatis框架为我们提供了方法Resources.getResourceAsReader()来读取资源。一旦有了Reader对象来读取DAOMap.xml,就能将它们读取至DAOManagerBuilder.buildDaoManager(),返回一个DaoManager实例,将来用于与DAO框架进行交互。从理论上来说,应该在项目启动的时候初始化DAO框架,在我们这个程序中,可以将这个模块放入Struts插件中,但是为了简化这个例子,我们将初始化模块放入execute方法中。
有了DaoManager实例后,可以调用相应的接口和存储实现类(在<context>元素中的id属性值)的getDao()方法。在我们的例子中,需要一个SQLMapContactDAO的实例,所以以ContactDAO为接口名称,“sqlmap”为存储机制。一旦实现了SQLMapContactDAO实例,就可以在调用其中的事务方法。
在最后的资源章节中可以下载到这个例子的源码。
DAO框架架构由于有了一个可以运行的示例,让我们得以粗略了解DAO框架是如何运作的。在图2表示的顺序图中演示了DAO的工作方式:
图2. DAO顺序图
在开始时,调用DaoManagerBuilder.buildDaoManager()并传入DAOMap.xml来初始化DAO框架。在这个方法中DAO框架会读取DAOMap.xml并且由此生成相应的DAOManager对象。这个对象包括了对支持的数据存储机制的描述。哪个接口会被实现,哪个是接口和存储机制结合的实现类?基本上这是和DAOMap.xml文件相等的 Java对象。
当有了DAOManager对象,可以从中得到ContactDAO接口的SQL Map实例。DAO框架会返回一个包装了实现类的DaoProxy对象。在本例子中将给SQLMapContactDAO返回一个DaoProxy对象。这个DaoProxy对象允许DAO框架截获调用商业方法。本例中,当调用 contactDAO.selectContact()时,DAO框架会截获这个调用并检查事务处理是否已经开始执行,如果没有,它将调用事务管理器中的startTransaction()创建一个新的事务处理调用。如果处理已经开始,DaoProxy对象会调用事务中的SQLMapContactDAO中的selectContact()方法。当selectContact()调用返回的时候,DaoProxy对象截获返回并提交给事务。
如果不希望事务在方法层上可见,或者希望在一个事务中调用多个不同的方法,则可在调用ContactDAO中的商业方法前调用daoManager.startTransaction(),然后在daoManager.startTransaction()执行完以后再提交商业方法。
那么现在剩下要关心的事情就是那个模块负责存储机制的初始化并传递控制给存储机制。在这个例子中,就意味着由哪个模块负责将SqlMapConfig.xml的路径传递给SQL Map框架并给它初始化。同样意味着哪个模块负责和SQL Maps框架进行实际的交互。DAO框架为每种存储提供了Template类,在工程中,可以从这个Template类中继承实例类,并只要自己的方法中编写商业事务逻辑。然后将控制传递给这个模板类,它将负责和存储机制的交互。在我们的例子中调用super.queryForObject("getContact",new Integer(contactId)),意味着SqlMapDaoTemplate将负责SQL Maps的初始化和与之交互。
初始化存储机制需要相关的一些信息,在例子中初始化需要SqlMapConfig.xml的路径,这个文件中包含驱动类的名字、JDBC URL、登陆信息之类的信息。这些特定的事务管理器需要的信息将会在DaoMap.xml文件中作为一个属性元素传递给管理器。下一节,我们将讨论DAO框架支持哪些事务管理器,每个管理器需要哪些初始化信息。
支持的存储管理机制DAO框架提供了内置的对一些存储管理机制的支持。为了使用其中的一个内置的transactionManagers,需要做两件事情:
1.在DAOMap.xml中增加一个<transactionManager>元素来声明对存储管理机制的支持。
2.在生成DAO实现类的时候为transactionManager继承适当的Template类。
下面我们要研究内置transactionManagers并找出在应用程序中使用如何使用它们。
JDBC如果不想使用任何存储框架,不想自己写JDBC代码,那么JDBC事务管理器是很好的选择。如果使用JDBC作为存储机制,则可以使用以下三种连接管理之一:
SIMPLE:如果要使用iBatis'自己的连接池实例,可以把SIMPLE作为DataSource元素的值。将通常的JDBC属性(DriverManager类, JDBC URL,等等)传入作为Properties。在iBatis在线文档中查看更多的连接属性。
清单5:<transactionManager type="JDBC">
<property name="DataSource" value="SIMPLE"/>
<property name="JDBC.Driver"
value="com.ibm.db2j.jdbc.DB2jDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:db2j:D:\cloudscape\wpsdb"/>
<property name="JDBC.Username"
value="db2admin"/>
<property name="JDBC.Password"
value="db2admin"/>
<property name="JDBC.DefaultAutoCommit"
value="true" />
</transactionManager>
DBCP:使用Apache DBCP作为连接管理。请查看DAO在线指导获得如何配置DBCP连接池的信息。
JNDI:当要使用应用服务器的连接池,那么要做的是提供连接池的JNDI名,DAO框架则使用这个名称获得一个连接。
清单6:<transactionManager type="JDBC">
<property name="DataSource" value="JNDI"/>
<property name="DBJndiContext"
value="java:comp/env/jdbc/MyDataSource"/>
</transactionManager>
然后要建立一个类继承JdbcDaoTemplate.java来实现事务方法借口。在示例中,我们建立了JDBCContactDAO.java。在事务方法中,可以调用getConnection()向父类请求连接。因为我们没有使用任何存储框架,所以我们只能建立并执行我们自己的SQL请求。
清单7:public int updateContact(Contact contact) {
try {
Connection conn = getConnection();
PreparedStatement updateStmt =
conn.prepareStatement("UPDATE DB2ADMIN.CONTACT
SET FIRSTNAME=?,LASTNAME=? WHERE CONTACTID=?");
updateStmt.setString(1, contact.getFirstName());
updateStmt.setString(2, contact.getLastName());
updateStmt.setInt(3, contact.getContactId());
return updateStmt.executeUpdate();
} catch (SQLException ex) {
throw new DaoException(ex);
}
}
使用JDBC transactionManager的时候,DAO框架会调用Connection 对象中的commit和rollback方法来控制事务处理。所以事务会在Connection层被处理,而不参与全局事务处理。
JTA如果项目是J2EE应用,那么使用应用服务器提供的连接池会更有利,因为它将比SIMPLE 或者DBCP 连接池有更好的性能。同样的,使用J2EE应用,RDBMS是唯一的处理源,除了RDBMS还需要包含JCA、MQ Server等功能。因为不能在连接层开始和处理事务,而要特别的在全局事务处理时在一个UserTransaction对象中调用begin()和commit()方法。所以对于这类请求,可以使用JTA 作为transctionManager,既可以向JNDI URL提供数据源连接池,也可以在里面包含UserTransaction对象。
清单8:<transactionManager type="JTA">
<property name="DBJndiContext"
value="java:comp/env/jdbc/MyDataSource"/>
<property name="UserTransaction"
value="java:comp/env/UserTransaction"/>
</transactionManager>
Hibernate因为Hibernate是很常见的存储框架,iBatis DAO也提供了对它的支持。为了在项目中使用Hibernate,像下面那样在DAOMap.xml增加<transactionManager>元素:
清单9:<transactionManager type="HIBERNATE">
<property name="hibernate.dialect"
value="net.sf.hibernate.dialect.Cloudscape"/>
<property name="hibernate.connection.driver_class"
value="com.ibm.db2j.jdbc.DB2jDriver"/>
<property name="hibernate.connection.url"
value="jdbc:db2j:D:\cloudscape\wpsdb"/>
<property name="hibernate.connection.username"
value="db2admin/>
<property name="hibernate.connection.password"
value="db2admin"/>
<property name="class.1"
value="com.sample.contact.Contact"/>
</transactionManager>
同样的,需要建立一个DAO类继承HibernateDaoTemplate。在这个DAO内,可以通过调用getSession()方法来获得Hibernate Session对象的入口。
SQL MAP请查看示例(在资源小节中)了解如何在项目中使用SQL Map存储框架的细节。
外部管理外部的事务管理器允许事务处理在外部被DAO框架控制。这种行为有利于处理和非关系数据库数据源的交互。下一节,我们将讨论如何用DAO框架处理以XML文件作为数据源的情况。
部署xml事务Map你可能也经常遇到这种情况:需要从xml中读取数据,而不是从RDBMS中读取,假想你正在从事一个银行项目,你并不能够直接接触到银行的数据库,所有的用户信息暂时都会通过一个XML文件传输给你,你必须使用这个XML文件进行开发,开发完毕再部署到真正的使用RDBMS的环境中,
这样的话,你需要做一下改变:
1. 在DAOMap.xml 中增加对外部的transactionManager 的支持。
2. 新建一个XMLContactDAO.java文件:
清单10:public class XMLContactDAO implements ContactDAO {
public static final String
CONTACTXMLNAME = "c:\\Contact.xml";
public XMLContactDAO(DaoManager manager) {
super(manager);
}
public int insertContact(Contact contact) {
HashMap contactMap = loadChanges();
if (contactMap.get(new Integer
(contact.getContactId())) == null)
contactMap.put(new
Integer(contact.getContactId()), contact);
saveChanges(contactMap);
return contact.getContactId();
}
public Contact selectContact(int contactId) {
HashMap contactMap = loadChanges();
return (Contact) contactMap.get(
new Integer(contactId));
}
public HashMap loadChanges() {
HashMap contactMap = null;
try {
XStream xstream = new XStream(new DomDriver());
xstream.alias("contact", Contact.class);
contactMap =
(HashMap) xstream.fromXML(
new FileReader(CONTACTXMLNAME),HashMap.class);
} catch (FileNotFoundException e) {
e.printStackTrace();
return new HashMap();
}
return contactMap;
}
public void saveChanges(HashMap contactMap) {
try {
XStream xstream = new XStream();
xstream.alias("contact", Contact.class);
xstream.toXML(contactMap,
new FileWriter(CONTACTXMLNAME));
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个例子中,XMLContactDAO实现了ContactDAO事务接口。因为我们使用了一个EXTERNAL事务管理器,所以不能使用任何已经存在的Template类。在我们的类中,我们使用XStream框架新建了两个简单的方法——loadChanges()和saveChanges()——实现对XML文件的读写。XStream是一个开源框架,实现将一个XML文件看作一个对象来读取,将对象保存为XML文件的功能。
结论当今,有很多新的存储框架出现。这对于一个程序员既有好处也有坏处。好处是有更多的选择余地。坏处是因为你必须作出一个选择,更糟糕的是不得不在项目开始的时候就选择一种框架,这就意味着你可能不能完全清楚的了解项目的需求,或者不能完全确信这种框架是否能完全满足项目的需求。DAO是一种容易使用并且功能强大的框架能够处理存储机制的改变。你在前期作出了付出,但是它肯定会在最后对你有帮助的。
资源·Matrix-Java开发者社区:
http://www.matrix.org.cn·onjava.com:
onjava.com·这篇文章的示例代码:(注:与译文放入同一压缩包中):
http://www.onjava.com/onjava/2005/08/10/examples/SampleDAO.zip·iBatis主页:
http://ibatis.apache.org/·核心 J2EE 模式:数据存储对象:
http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html·Hibernate主页 (或者CodeZoo: Hibernate) :
http://www.hibernate.org/·使用XStream序列化Java对象:
http://www.xml.com/pub/a/2004/08/18/xstream.html ·XStream (或者 CodeZoo: XStream) :http://xstream.codehaus.org/ ( http://www.codezoo.com/ :http://www.codezoo.com/pub/component/3551 )
关于作者Sunil Patil对J2EE技术领域的研究超过5年时间。他对感兴趣的领域是与对象相关的映射工具、UI框架和Portals