电脑技术学习

使用SQLMaps进行对象关系映射

dn001
内容:
使用SQLMaps进行对象关系映射

作者:Sunil Patil

翻译:yuchen(yuyu1984)


版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:
Sunil Patil;yuchen(yuyu1984)
原文地址:
http://www.onjava.com/pub/a/onjava/2005/02/02/sqlmaps.htm
中文地址:
http://www.matrix.org.cn/resource/article/43/43822_SQLMaps.html
关键词: SQLMaps OO Mapping


引论

随着Hibernate逐渐成为其他架构之首,现今的许多工作都是在对象关系(OR)映射域上进行。但是,那些对象关系映射工具有一个问题:大部分数据库管理员似乎对这些对象关系(OR)映射工具所产生的查询不放心。不幸的是,这些数据库管理员不了解,架构所产生的查询是多么的优越,并且使你的应用程序更加灵活。他们觉得随着数据库成为应用程序的主要瓶颈,应该完全控制SQL查询,以便他们能分析和调试以促进性能。

但有个问题是,如果不使用对象关系(OR)映射工具,那么你将花费很多资源在书面形式上和维护低级JDBC代码。由于以下原因,每个JDBC应用程序都将含有重复代码:
1.连接和事务管理。
2.设置java对象为查询参数。
3.转换SQL ResultSets 为 java 对象。
4.生成查询字符串。

iBatis 的 SQLMaps 架构帮助我们在相当大的程度上减少了正常情况下访问关系数据库的java代码的总量。它考虑到了当前三个比较关心的问题,所以它可以将一个简单的 JavaBean 对象映射到 PreparedStatement 参数和 ResultSet 值。SQLMaps 的原理其实很简单:提供一个简单的架构来提供80%的JDBC功能。

这篇文章是关于如何使用 SQLMaps 架构的进阶辅导。我们将建立一个简单的 Struts 应用程序并且用 SQLMaps 配置它为开始。接着,我们将演示如何执行基本的数据库操作比如 SELECT, INSERT, UPDATE 等等。然后,我们将演示 SQLMaps 为连接和事务管理提供的操作。在最后,我们将试着使用一些高级的 SQLMaps 功能部件比如缓存和分页。

SQLMaps 的基本思想

使用 SQLMaps 架构,你必须建立一个XML文件列出所有你希望在应用程序中执行的SQL查询。对于每个SQL查询,你来指定哪个java类可以交换参数和ResultSet(结果集)。

在你的java代码中,如果你想要执行一个特殊的查询,你需要创建一个对象来传递查询参数和必要的条件,然后传递这个对象和查询的名字让 SQLMaps 执行。一旦查询被执行,SQLMaps 将为你指定的接受查询结果的类创建一个句柄,并且将它和数据库返回的ResultSet 中的值存储在一起。

一个简单的使用SQLMaps的应用程序(Hello World)

我们将创建一个简单的Struts应用程序开始来演示什么是使用SQLMaps的应用程序所需要的。这个例子的代码是来自以下资源部分。在这个例子中,我们用一个Jsp页面要求用户输入contactId属性。一旦它被提交,我们用它在CONTACT 表中搜索一个连接,并且用另一个Jsp页面展示给用户。接下来一步一步说明:

1. 拷贝ibatis-sqlmap-2.jar 和 ibatis-common-2.jar 到你的 web-inf/lib 文件夹中。

2. 在你的java源文件夹中创建一个 SqlMapConfig.xml 文件,像这样:



<transactionManager type="JDBC">

value="COM.ibm.db2.jdbc.app.DB2Driver"/>
value="jdbc:db2:SAMPLE"/>
value="db2admin"/>
value="admin2db"/>

</transactionManager>



SqlMapConfig.xml 是SQLMaps的部署描述信息,包括以下元素:

是文件的根元素。被用来定义应用程序级别的设置;例如 useStatementNamespaces 属性被用来定义是否要用准备说明的全限定名。它可以有一些另外的属性用于控制缓存和初始化;要知道更进一步的细节请查看文档。

<transactionManager> 被用来定义你想要在你的应用程序中使用的事务管理。在我们的例程中,我们要用Connection 对象的 commit 和 rollback 方法来管理事务,所以我们用JDBC作为事项管理程序。它包含 作为子元素,用来定义你要使用的连接管理的类型。在我们的例程中,我们要用SQLMaps自带的连接池实现,如此我们将使用一个SIMPLE类型的数据源。为了建立连接池,SQLMaps要求像JDBC驱动名称,URL,和密码这些信息,因此,我们使用元素来传递这些信息。稍后,我们将更加详细地讨论各种各样可用的事务和连接管理选项。

元素被用来表明 SQLMap 配置文件。这些文件,如早先讨论的,列出了你希望执行的SQL查询。

3. 创建一个JavaBean类,Contact.java,有属性 firstName, lastName 和 contactId 和相应的get、set方法。这个类将被用来向ResultSet传递查询参数并读取值。

public class Contact implements Serializable{
private String firstName;
private String lastName;
private int contactId;
//Getter setter methods for firstName,
//lastName and contactId property
}


4. 建立一个Contact.xml文件,我们将在文件中列出所有要执行的与表Contact有关的SQL查询。


type="com.sample.contact.Contact"/">
<select id="getContact"

select CONTACTID as contactId,
FIRSTNAME as firstName,
LASTNAME as lastName from
ADMINISTRATOR.CONTACT where CONTACTID = #id#
</select>


文件中使用的标签如下:

是文件的根元素。正常情况下,你的应用程序将有不止一个表,由于你要把与不同表有关的查询分开成不同的名称空间(namespace), 元素就是被用来指定在此文件中查询应被放置的名称空间。

用来说明Contact 类的全限定名的一个简称。在此说明后,这个简称可以被用来替代全限定名。

在SQLMaps架构中 <select> 元素用来声明SQLMaps架构中的SELECT查询。它的值就是你能指定的要执行的查询。id属性被用来指定通知SQLMaps 执行特殊查询的名称。parameterClass 被用来指定传递查询参数的类,resultClass 提供从ResultSet返回值的类的名称。

5. 在Action类的 execute() 方法内,我们建立了一个SqlMapClient的句柄,它被用来和SQLMaps相互作用。我们必须向SqlMapClientBuilder传递SqlMapConfig.xml文件,它被用来读取配置设置。

DynaActionForm contactForm =
(DynaActionForm)form;
Reader configReader =
Resources.getResourceAsReader("SqlMapConfig.xml");
SqlMapClient sqlMap =
SqlMapClientBuilder.buildSqlMapClient(configReader);
Contact contact = (Contact)
sqlMap.queryForObject("getContact",
contactForm.get("contactId"));
request.setAttribute("contactDetail", contact);
return mapping.findForward("success");


当你要执行一个SELECT 查询时,应该使用 SQLMaps 的 queryForObject 方法。在Contact.xml文件中,我们已经指定parameterClass为int,所以我们在传递查询的名称的时候传递一个integer作为contactId (i.e getContact)。

SQLMaps 将返回一个Contact 类的对象。


基本数据库操作

现在我们要返回我们的所关注的使用SQMLaps如何表示一些基本的数据库操作。

1. Insert操作
我们从如何执行一个开始Insert操作开始。


INSERT INTO ADMINISTRATOR.CONTACT( CONTACTID,FIRSTNAME,LASTNAME)
VALUES(#contactId#,#firstName#,#lastName#);


元素被用来声明一个Insert的SQL查询。它有一个parameterClass 属性用来指明哪个JavaBean类将被用来传递(request)请求参数。在插入新的记录时,我们要使用contactId属性的值,所以我们必须在SQL查询中使用一个#contactId#。

public void contactInsert() throws SQLException, IOException {
sqlMap.startTransaction();
try {
sqlMap.startTransaction();
Contact contact = new Contact();
contact.setContactId(3);
contact.setFirstName("John");
contact.setLastName("Doe");
sqlMap.insert("insertContact",contact);
sqlMap.commitTransaction();
} finally{
sqlMap.endTransaction();
}
}


在我们的java代码中,我们建立了一个Contact 对象,存放它的值,然后调用sqlMap.insert()方法,传递我们要执行的查询的名称和 Contact 。这个方法将插入一个新的Contact并且返回新插入的Contact的主键。

缺省情况下,SQLMaps 把每个DML方法当作工作的一个单元。但是你能使用 startTransaction, commitTransaction 和 endTransaction 方法来划分事务处理。通过调用 startTransaction() 方法你可以启动一个事务处理,此方法也能从连接池中获得一个连接。在这个事务中,这个连接对象将被用来执行查询。如果这个事务的所有查询都被成功执行,应该调用 commitTransaction 方法提交你的改动。不考虑你的事务成功与否,最后都要调用 endTransaction 方法,将连接对象返回连接池,同时,此方法对于恰当的清理工作是必要的。

2.Update
元素被用来声明一个更新查询。它的 parameterClass 元素被用来声明传递查询参数的 JavaBean 的名称。在你的java代码中,你可以用sqlMap.update("updateContact",contact)指示 SQLMaps 来触发一个update查询。这个方法将返回被更改行的数目。


update ADMINISTRATOR.CONTACT SET
FIRSTNAME=#firstName# ,
LASTNAME=#lastName#
where contactid=#contactId#


3.Delete
元素被用来声明一个DELETE操作。在你的java类中,执行如下操作: sqlMap.delete("deleteContact",new Integer(contactId)) 方法返回被更改行的数目。


DELETE FROM ADMINISTRATOR.CONTACT WHERE CONTACTID=#contactId#


4.Procedure
procedure 元素支持存储过程。大部分存储过程要一些类似IN, INOUT 或者 OUT 的参数。所以,创建一个 元素并列出你要向存储过程传递的参数。只有当参数类型为OUT或者INOUT时parameterMap 对象才会被改变。



javaType="java.lang.Integer" mode="IN"/>
javaType="java.lang.String" mode="IN"/>
javaType="java.lang.String" mode="IN"/>



{call swap_contact_name (?, ?,?)}


你的代码一开始就要创建一个你要传递给存储过程的参数的 HashMap ,然后把它和你要执行的查询的名称一同传递给 sqlMap 。

HashMap paramMap = new HashMap();
paramMap.put("contactId", new Integer(1));
paramMap.put("firstName", "Sunil");
paramMap.put("lastName", "Patil");
sqlMap.queryForObject("swapCustomerName", paramMap);


连接和事务管理

SQLMaps 架构已经为你考虑倒了连接管理。缺省情况下,它附带有连接管理的3种不同的实现。你可以通过 元素的类型(type)属性的值指定你要使用的实现。

SIMPLE: 使用SQLMaps 自带的连接池实现。当使用这个实现时,我们必须传递连接信息(比如JDBC驱动名,用户名,密码)给SQLMaps。
DBCP: 使用Apache 的 DBCP 连接池算法。
JNDI: 使用一个容器提供数据源。如果你要使用这个方法,首先配置容器中的JDBC数据源(用一些特殊的方法),然后像这样指定数据源的JNDI名:

<transactionManager type="JDBC" >

value="java:comp/env/jdbc/testDB"/>

</transactionManager>


DataSource 特性的值应该指向你要使用的数据源的 JNDI 名称。

SQLMaps 使用 DataSourceFactory 来实现连接管理,所以你可以建立你自己的类实现这些接口并通知SQLMaps 使用它,如果你愿意的话。

对于事务管理,SqlMapConfig.xml文件中 <transactionManager> 元素的值指明了哪个类将被用来处理事务管理。

JDBC: 在这种情况下,通过调用Connection 对象的begin和commit方法来控制事务。这一选择被用在容器外运行的应用程序上并且和单独的数据库相互作用。
JTA: 在此情况下,使用一个全局的JTA事务。 SQLMaps 的活动只是一个广范的可能涉及其他数据库和事务源的事务的一部分。
External: 在此情况下,你要自己管理事务。最为架构生存周期的一部分,事务是不会被提交或回滚的。这个设置对只读数据库很有用。

高级特征
现在,我们花点时间讨论一下SQLMaps 架构的高级特征。这篇文章的范围不允许我覆盖全部的特征,所以我将讨论一些我认为经常用到的内容;你可以查看SQLMaps文档查找其所支持的特征。

Caching
元素被用来描述一个使用(query-mapped)查询映射语句的缓存。

12345678901234567890123456789012345678901234567890







<select id="getCachedContact" parameterClass="int"

select FIRSTNAME as firstName,LASTNAME as lastName
from CONTACT where CONTACTID = #contactId#
</select>


每个查询都有一个不同的缓存模型,或者几个查询共享一个缓存。SQLMaps 提供不同缓存类型的可插入的架构。cacheModel 元素的type属性指明了应该使用的实现。

LRU: 当缓存满了的时候,从缓存中移出最少用到的元素。
FIFO: 当缓存满了的时候,将建立时间最长的对象移出缓存。
MEMORY: 使用java引用类型像SOFT, WEAK 和 STRONG 来管理缓存特性。它允许垃圾收集器来决定是驻留在内存还是被销毁。这种实现应该使用在可用内存少的应用程序中。
OSCACHE: 一个OSCache2.0缓存引擎的插件。需要将oscache.properties放在根文件夹中来配置OSCache。这个实现可以被用到分布式应用程序中。

<select>元素的cacheModel属性定义哪个缓存模型用来存放结果。通过设置的属性cacheModelsEnabled的值为false可以禁止SqlMapClient 全部的缓存。

如何允许记录日志

SQLMaps通过使用Jakarta Commons logging架构来提供记录日志信息,下面几步介绍如何允许记录日志:

1. 添加log4j.jar到你的应用程序的classpath所设置的目录中。对于一个web应用程序,你将需要拷贝此文件到WEB-INF/lib目录中。
2. 在你的应用程序的classpath所设置的目录中,建立一个log4j.properties文件如下:


log4j.rootLogger=ERROR, stdout
# SqlMap logging configuration...
log4j.logger.com.ibatis=DEBUG
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n



分页

假设CONTACT 表中有1000条记录并且要在一个电子表格中以每次50条展示给用户。在此情况下,我们不想去查询CONTACT 表得到一个包含1000条记录的ResultSet 对象;我们要每次查询CONTACT 得到50条记录。SQLMaps 提供PaginatedList 接口来解决像这样的情况。它允许你以一个用户可以前后定位的数据子集来解决。


PaginatedList list = sqlMap.queryForPaginatedList("getContacts", null, 2);
while (true) {
Iterator listIterator = list.iterator();
while (listIterator.hasNext()) {
System.out.println(
((Contact)listIterator.next()).getContactId());
}
if( list.isNextPageAvailable())
list.nextPage();
else
break;
}



结论

如果你的应用程序有一个小的固定的查询数目,SQLMaps 是一个非常好的选择。它简单易用,并且可以更有效的利用开发者关于SQL的知识。程序员列出所有需要的查询并且开始java代码方面的工作,把SQLMaps的XML文件交给数据库管理员,他会试着分析并且调试查询,SQLMaps还能帮助你完成角色分离。

优点:
1.不依赖于一个OR映射框架所支持的Dialects。
2.使用很简单;支持许多高级特性。
3.无需学习像EJBQL这样的新查询语言。允许你从已有的SQL知识中获益。

缺点:
1.如果你使用了高级特性,应用可能不能移植。

但如果你的应用拟在多个数据库上工作,或者有大量的查询,在做出最后的决定之前,你可能需要研究几个OR映射框架。

资源
本文的例子代码:
Download File Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd

标签: