Spring与iBATIS的集成
iBATIS似乎已远离众说纷纭的OR框架之列,通常人们对非常流行的Hibernate情有独钟。但正如Spring A Developer's Notebook作者Bruce Tate 和Justin Gehtland所说的那样,与其他的OR框架相比,iBATIS独辟蹊径“iBATIS不是试图从根本上掩盖SQL,实际上,它是在拥抱SQL。”
但别犯愁:SQL本身具备了一些重要的功能,并且通过模板的使用,在Spring应用中采用iBATIS显得轻而易举。在此摘录中,两位作者将和你一起安装iBATIS并将其集成进你的Spring应用中。他们也阐明了怎样取得你已编写的SQL语句及把他们映射给iBATIS使用的Bean。最后,还讨论了iBATIS的优缺点,及为什么是这样的一种方式,即所谓的“在完全OR与JDBC之间的一个幽雅的折衷。”
就象Developer's Notebook 系列中所有的书一样,你将由直接的且实用的方式获得信息,这些信息包含了完成你工作所需的细节。换言之:“一切来自实战,没有讲义。”
对象关系持久化(OR Persistence)
本章所涉及的内容为:
· 与iBATIS的集成
· 在Spring的应用中使用JDO
· 在Spring的应用中使用Hibernate
· 运行测试用例
离我住处不远的地方,名为啤酒山的山上有一个臭名远扬的山地自行车道。我想不出它为何会得到这样的名字,因为在下山的时候你要保持完全地清醒甚至于全神贯注。据我所知,那决不是纯粹的攀爬(或不曾离开自行车步行)。大多数人认为那很荒谬,哪怕只是去尝试一下而已。这座山连绵起伏,异常陡峭,它的表面覆盖了松散的岩石和大量的岩脊。我想我一定是太厌烦我的余生了,因为两年半来我一直不断地在攀爬。在我看来,那样的一座山峰就像是一个困难的编程问题。我已多次重新启程,在到达山峰的途中,尝试过翻越许多岩脊和泥砾丛的不同途径。为了攀爬啤酒山, 我需要改良自行车,提高车技,加强训练。
对Java 开发人员而言,对象关系映射已然成为那类问题了。我们需要处理功能(processing power)、较佳设计模式及更好持久化框架的组合,从而使解决持久化设计变得更加清晰。最终,我们开始取得实质性的进展。现在,我们已逼近山峰,象Spring这样的框架就象是方程式的一部分,因为他们让对象关系映射(ORM)花较少的精力来做更多的事情。在本章中,你将看到三种框架:iBATIS, JDO及Hibernate
·iBATIS 是一种JDBC助手类框架,它给了你一些OR映射及使用OR的好处,但不带 有太多的风险。
·JDO是一种饱受争议的持久化标准,但它有着一些独特的实现,这使得JDO在行业中也算得上是最完美的持久化框架之一。
·Hibernate 是在JBoss组织控制下的一开源持久化框架。它可能是时下最为流行的持久化框架。大量的客户采用它,而且客户的数量还在持续稳定地增长。
在本章中,我们会对所提的三个框架进行尝试,而不必对应用的其余部分的代码做任何变更。
与iBATIS集成
常言道“树大招风”。在大肆宣扬的J2EE和.Net之争中, Sun的一示例应用程序被用来作为衡量应用服务器在运行时的各项性能的核心基准。 .Net非常好地打败了基于EJB的J2EE版本, 且方兴未艾。Clinton Begin 开发了iBATIS的持久化框架,他在iBATIS中使用PetStore的简化版,且自从那以后iBATIS就越来越流行。Spring提供非常优良的与iBATIS的集成功能, 在此章中将对其进行介绍。
并非所有的问题都非常适合持久化框架这朵盛开的花朵,中等难度的问题是最合适的环境。没有正确的技巧或不相称的问题,可能就会误入歧途。跟我一起教课的Ted Neward, 他是《Effective Enterprise Java》一书的作者,他经常把构建或采用持久化框架与美国的越南战争拿来做比较,进入这样的两场战争是很诱人的,但最终都很难赢得胜利,而且此二例中并不存在着行之有效的策略。关于这一话题,你仍可查阅httpwww.neward.net上的相关内容。
然而,我不想扯得太远。尝试一下象iBATIS SqlMaps的框架给了你OR的使用模型又何尝不可呢?当然,我们不会强迫你一口吞下一只大象。具体来说,iBATIS让你:
·映射字段和SQL语句到关键字
·使用SQL的全部功能而没有乏味的JDBC
·从你的代码中剥离SQL
Spring 与iBATIS的集成给了你这些及更多的裨益, 让我们为此而忙乎起来。
我该怎么做?
首先,你需要安装iBATIS。由于iBATIS的配置会在你的Spring 应用上下文中完成,因此你不需要立即配置它。在httpwww.ibatis.com(译注:最新的网址为:httpibatis.apache.org,iBATIS已于2004-08-16并入Apache 软件基金会)上可以下载并安装。在本书中我们使用1.3.1版本。把iBATIS提供的jars(ibatis-sqlmap.jar, ibatis-dao.jar, and ibatis-common.jar)和Spring提供的jdom.jar(在Spring的lib 目录下)放到你的项目目录warWEB-INFlib中。
You’ve already got the interface for the façade and the model, so you need an implementation of the façade and the SQL statement. First, you can implement the façade for the application, as in Example 5-1.
你已经有了门户(façade)和模型(model)的接口,因而你需要facade和SQL语句的实现。首先,你可以象示例5-1那样,在你的应用中实现façade的接口。
示例 5-1 IBatisRentABike.java
public class IBatisRentABike extends SqlMapDaoSupport
implements RentABike {
private String storeName =;
public void setStoreName(String storeName) {
this.storeName= storeName;
}
public String getStoreName( ) {
return this.storeName;
}
public List getBikes() {
return getSqlMapTemplate().executeQueryForList(getBikes, null);
}
public Bike getBike(String serialNo) {
return (Bike) getSqlMapTemplate().
executeQueryForObject(getBikeBySerialNo, serialNo);
}
public Bike getBike(int bikeId) {
return (Bike) getSqlMapTemplate().
executeQueryForObject(getBikeByID, new Integer(bikeId));
}
public void saveBike(Bike bike) {
getSqlMapTemplate().executeUpdate(saveBike, bike);
}
public void deleteBike(Bike bike) {
getSqlMapTemplate().executeUpdate(deleteBike, bike);
}
public List getCustomers() {
return getSqlMapTemplate().executeQueryForList(getCustomers, null);
}
public Customer getCustomer(int custId) {
return (Customer) getSqlMapTemplate().
executeQueryForObject(getCustomer, new Integer(custId));
}
public List getReservations() {
return getSqlMaptemplate().
executeQueryForList(getReservations, null);
}
public List getReservations(Customer customer) {
return getSqlMaptemplate().
executeQueryForList(getReservationsForCustomer, customer);
}
public List getReservations(Bike bike) {
return getSqlMaptemplate().
executeQueryForList(getReservationsForBike,bike);
}
public List getReservations(Date date) {
return getSqlMaptemplate().
executeQueryForList(getReservationsForDate, date);
}
public Reservation getReservation(int resId) {
return getSqlMaptemplate().
executeQueryForObject(getReservation, new Integer(resId));
}
}
这些就是命名式查询。iBATIS将每一查询分成一个独立的映射,那样你就可以用名字来执行查询。
SqlMapTemplate由Spring的SqlMapDaoSupport 类提供,我们的RentABike实现必须来继承这个类。SqlMapTemplate负责建立和管理底层数据存储的连接,同时也解释了你所提供的映射文件。你可以把template 看成是你对于iBATIS命名式查询所做那些事情的默认实现。
你也需要创建SQL语句,可以给每条SQL语句取一个名字。然后,把结果映射给Java Bean。在这里你有两种选择,你可以在SQL中把每个Bean属性作为别名来引用,或在查询和Bean 之间建立显式映射,就象示例5-2那样。在此例中我们也建立了Customer 与Reservation的映射。
示例5-2. Bike.xml(iBATIS SQL 映射文件)
xml version=1.0 encoding=UTF-8
sql-map
result-map
property column=bikeId columnIndex=1
property column=manufacturer columnIndex=2
property column=model columnIndex=3
property column=frame columnIndex=4
property column=serialNo columnIndex=5
property column=weight columnIndex=6
property column=status columnIndex=7
result-map
mapped-statement result-map=result
select bikeId, manufacturer, model, frame, serialNo, status
from bikes
mapped-statement
mapped-statement result-map=result
select bikeId, manufacturer, model, frame, serialNo, status
from bikes
where serialNo=#value#
mapped-statement
mapped-statement result-map=result
select bikeId, manufacturer, model, frame, serialNo, weight, status
from bikes
where bikeId=#value#
mapped-statement
mapped-statement
insert into bikes
(bikeId, manufacturer, model, frame, serialNo, weight, status)
values(#bikeId#, #manufacturer#, #model#, #frame#, #serialNo#,
#weight#, #status#)
mapped-statement
mapped-statement
delete from bikes
where bikeId = #bikeId#
mapped-statement
sql-map
示例5-3. Customer.xml
xml version=1.0 encoding=UTF-8
sql-map
result-map
property column=custId columnIndex=1
property column=firstName columnIndex=2
property column=lastName columnIndex=3
result-map
mapped-statement result-map=result
select custId,
firstName,
lastName
from customers
mapped-statement
mapped-statement result-map=result
select custId,
firstName,
lastName
from customers
where custId = #value#
mapped-statement
sql-map
示例 5-4. Reservation.xml
xml version=1.0 encoding=UTF-8
sql-map
result-map
property column=resId columnIndex=1
property column=bikeId columnIndex=2
property column=custId columnIndex=3
property column=resDate columnIndex=4
result-map
mapped-statement result-map=result
select resId,
bikeId,
custId,
resDate
from reservations
mapped-statement
mapped-statement result-map=result
select resId,
bikeId,
custId,
resDate
from reservations
where custId = #value#
mapped-statement
mapped-statement result-map=result
select resId,
bikeId,
custId,
resDate
from reservations
where bikeId = #value#
mapped-statement
mapped-statement result-map=result
select resId,
bikeId,
custId,
resDate
from reservations
where resDate = #value#
mapped-statement
mapped-statement result-map=result
select resId,
bikeId,
custId,
resDate
from reservations
where resId = #value#
mapped-statement
sql-map
The result-map portion provides an explicit map between columns in the database and properties of a persistent class. The mapped-statement can then simply define the SQL queries necessary to execute the needed functionality, and the map handles creation of the resultant Java object. In addition to the Bike version above, your application currently also requires a map for Customer and Reservation.
result-map部分提供了数据库字段与持久化类属性之间的一显式映射。mapped-statement接着可以简单定义运行所需功能的必要的SQL查询,而映射负责创建合成的Java 对象。除了上述的Bike 版本之外,你的应用程序目前也需要一个关于Customer 和Reservation的映射。
你将不得不做一些OR框架通常为我们所做的活,如创建标识符。在此例中,你将使用MySQL生成的序列(就是数据库表中的那些AUTO_INCREMENT字段)。你只需在数据库表定义时简单地把bikeId标为AUTO_INCREMENT,这样当增加一条新记录时,你就可以在SQL语句中略过bikeId字段。我们映射中SaveBike语句就成了示例5-5。
示例5-5. Bike.xml
mapped-statement
insert into bikes
(manufacturer, model, frame, serialNo, weight, status)
values(#manufacturer#, #model#, #frame#, #serialNo#, #weight#,
#status#)
mapped-statement
若你是在使用Oracle, Spring和iBATIS也支持Oracle生成的序列。
下一步,你可以更新应用上下文,且需要列出我们的新façade,而façade要有SQL映射。把SQL 引入PROPERTIES 文件,就象我们对待JDBC 的参数一样。 此外,你还需要配置事务策略。( 示例5-6)
示例5-6. .RentABikeApp-servlet.xml
beans
bean
class=org.springframework.jdbc.datasource.DriverManagerDataSource
property
valuecom.mysql.jdbc.Drivervalue
property
property
valuejdbcmysqllocalhostbikestorevalue
property
property
valuebikestorevalue
property
bean
bean
property valueBruce's Bikesvalueproperty
property ref local=dataSourceproperty
property ref local=sqlMapproperty
bean
bean
class=org.springframework.orm.ibatis.SqlMapFactoryBean
property
valueWEB-INFibatis.configvalue
property
bean
bean
class=org.springframework.jdbc.datasource.DataSourceTransactionManager
property ref local=dataSourceproperty
bean
这个事务策略就在应用上下文中,你不必来管理提交适宜,因为Spring 会为你完成。你将在第六章中更完整地探讨事务策略。
示例5-7. . ibatis.config
xml version=1.0 encoding=UTF-8
sql-map-config
sql-map resource=Bike.xml
sql-map resource=Customer.xml
sql-map resource=Reservation.xml
sql-map-config
稍后我们会谈到事务策略。
发生了什么事
你没看到OR映射。OR框架会有意地将一个数据库表与一个类或多个类关连起来。在此例中,iBATIS会将查询结果赋予一个类。这意味着iBATIS并未试图对你隐藏SQL细节。实际上,它是在拥抱SQL。
Spring打算通过模板来简化iBATIS的使用。iBATIS模板会给出类似JDBC模板的使用模型,你只需指定数据源和iBATIS映射的SQL语句。
当执行SQL语句时,Spring与iBATIS一起为你管理资源,并按要求来创建和关闭连接。Spring会把映射的SQL语句传给iBATIS, 由iBATIS来运行映射的语句,且若需要,iBATIS会把结果集传给给你映射SQL语句时所指定的Bean。如果你已有了任何参数,那就可将那些结果集放入hash map中并把结果集传给带有映射的SQL语句的模板。
然而,从内部所表现的来看,iBATIS或许并不象是一个OR框架,但使用模型的确有OR的趋向。 构建带有操作数据存储的数据访问对象(data access object),你不会在代码中看到SQL语句,因为它已被保存在配置文件中了,且iBATIS使用对象集合而不是结果集。简而言之,这是在全部OR与JDBC之间的一个幽雅的折衷。
关于…
一切都适合iBATIS吗? 由于这种使用模型与OR模型是那么的相似,且许多应用对他们可能生成的SQL需要有更多的控制,或许你倾向于普遍地使用iBATIS。尽管如此,象JDO和Hibernate的OR框架仍拥有着属于自己的舞台。OR框架给了你更多的灵活性和更强大的功能:
·一些高级的对象模型导致了非常复杂的SQL,把这些复杂的SQL交由ORM来完成是最好的处理方式。 例如,继承通常使得原始的JDBC API操作变得更为复杂。
·一些高级的性能特征,象延迟加载和数据抓取群组,要求对有效地的自动控制需要一个更正式的模型。然而,完美地优化后的JDBC至少要和ORM一样快速。相对于使用原始的JDBC而言,由ORM提供的性能优化选项对某些类型的问题更获得较佳性能。
·ORM使某些问题变得更加有趣。操作对象比创建一套的SQL查询更加简易。ORM很适合带有简单的查找,创建,更新,根据主键读取数据库中的数据以及删除记录的应用。
如若你的应用有快速的数据模型和对象模型的变更,iBATIS就会很适合。如果你有了CRUD类型的应用,那么,使用iBATIS可能就会有点乏味。相反地,如果你正寻找一种好的SQL访问方式及在ORM和原始的JDBC间有效的折衷,那么,iBATIS可能就会给了你所需的一切。