一. 引言
spring是一个轻量级的应用程序框架。在许多情况中,spring都能够良好地代换传统的由java ee应用程序服务器所提供的服务。spring既是综合性的也是模块化的。基于其分层架构,它能够使开发者灵活地单独使用其任何一部分。spring由许多模块组成,例如ioc容器,aop,mvc,持久性,dao和remoting。这些模块都是相当松耦合的:其中,一些模块的使用根本不需要另一些模块。以前,简直还没有象spring应用程序这样的:你可以选择使用一些,大多数,或所有的spring框架支持的组件来构建你的应用程序。
spring框架所提供的jdbc支持与其它spring部分并非是紧耦合的,这极有利于代码的可维护性。本文将向你展示任何直接使用jdbc(也即是,不通过一些o/r映射框架本身使用jdbc)的应用程序是如何从spring中受益的。
二. 传统型jdbc
传统型jdbc有许多积极的方面使之在许多j2se和j2ee应用程序开发中占有重要地位。然而,也有一些特征使其难于使用:
· 开发者需要处理大量复杂的任务和基础结构,例如大量的try-catch-finally-try-catch块。
· 应用程序需要复杂的错误处理以确定连接在使用后被正确关闭,这样以来使得代码变得冗长,膨胀,并且重复。
· jdbc中使用了极不明确性的sqlexception异常。
· jdbc没有引入具体的异常子类层次机制。
相应于任何一种错误,都只是抛出sqlexception异常-无论它来源于jdbc驱动程序还是来源于数据库,这使得程序员很难理解到底是哪里实际出现了错误。例如,如果sql对象是无效的或已经被锁定,那么将抛出一个sqlexception异常。调试这样的异常需要一定的时间来检查sql状态值和错误代码。更有甚者,sql状态值和错误代码的含义在各种数据库之间都有些差别。
事实证明,编写jdbc代码并不是一项容易的工作-存在大量的重复性的工作。为了说明问题,下面是一个例子-使用传统型jdbc来从数据库中得到一个可用任务的列表。
package com.spring.jdbc;
import java.sql.connection;
import java.sql.drivermanager;
import java.sql.preparedstatement;
import java.sql.resultset;
import java.sql.sqlexception;
import java.util.vector;
public class traditionaljdbc {
public vector gettasksnames() {
connection con = null;
preparedstatement pstmt = null;
resultset rs = null;
vector task = new vector();
try {
con = getconnection();
pstmt = con.preparestatement( "select taskname from tasks");
rs = pstmt.executequery();
while (rs.next()) {
task.add(rs.getstring(1));
}
} catch (sqlexception e) {
system.out.println(e);
} finally {
try {
rs.close();
pstmt.close();
con.close();
} catch (sqlexception e1) {
system.out.println(e1);
}
}
return task;
}
private connection getconnection()throws sqlexception {
try {
drivermanager.registerdriver(new oracle.jdbc.driver.oracledriver());
return drivermanager.getconnection("jdbc:oracle:thin:@localhost:1521:orcl",
"scott","tiger");
} catch (sqlexception sqle) {
system.out.println(sqle);
return null;
}
}
public static void main(string[] args) {
traditionaljdbc obj = new traditionaljdbc();
vector task = obj.gettasksnames();
for (int i = 0; i < task.size(); i++) {
system.out.println(task.elementat(i));
}
}
}
除了实际查询数据库的sql代码外,上面的示例中需要巨大数量的例程代码。getconnection()方法与我们的任务无关,而即使是gettasksnames()方法也仅包含特定于当前任务的两行代码。剩下的都是一些普通的复杂的任务代码。
jdbc的许多积极方面使得它在许多j2se和j2ee应用程序中仍然占有重要地位。然而,正如你所见,有一些特征使其比我们可能想像的要更难于使用。jdbc这些乏味并且有时挫败人性的特征已经导致出现了许多公共的可以利用的jdbc抽象框架(例如sqlexecutor和apache jakarta commons dbutils),还有数不清的自家生产性jdbc应用程序框架。一种公共的可以利用的jdbc抽象框架正是spring框架的jdbc抽象。
三. spring jdbc简介
spring所提供的jdbc抽象框架由四个不同的包组成:
· 核心包包含jdbctemplate。这个类是一个基础类之一-由spring框架的jdbc支持提供并使用。
· 数据源包是实现单元测试数据库存取代码的重要的一部分。它的drivermanagerdatasource能够以一种类似于你已经习惯于jdbc中的用法:只要创建一个新的drivermanagerdatasource并且调用setter方法来设置driverclassname,url,username和password。
· 对象包中包含类,用于描述rdbms查询、更改和存储过程为线程安全的、可重用的对象。
· 支持包-你可以从这里找到sqlexception翻译功能和一些工具类。
1) 模板设计模式
spring jdbc实现模板设计模式,这意味着,代码中的重复的复杂的任务部分是在模板类中实现的。这种方式简化了jdbc的使用,因为由它来处理资源的创建和释放。这有助于避免普通错误,例如忘记关闭连接等。它执行核心jdbc工作流任务,如语句创建和执行,而让应用程序代码来提供sql并且提取结果。
2) spring jdbc异常处理
spring框架特别强调在传统型jdbc编程中所面临的与下列方案有关的问题:
· spring提供一个抽象异常层,把冗长并且易出错误的异常处理从应用程序代码移到由框架来实现。框架负责所有的异常处理;应用程序代码则能够专注于使用适当的sql提取结果。
· spring提供了一个重要的异常类层次,以便于你的应用程序代码中可以使用恰当的sqlexception子类。
借助于一个抽象异常层,我们成功地实现了数据库独立性而不必改变异常处理。例如,如果你把你的数据库从postgresql改变为oracle,那么你不必把异常处理从oracledataexception改变到postgresdataexception。spring能够捕获应用程序服务器特定的异常并抛出一个spring数据异常。
当处理异常时,spring检查来自一个数据库连接的元数据可用性以决定数据库产品。它使用这种知识来把sqlexception映射到其自己异常层次中的具体的异常上。因此,我们不需要担心专门性的sql状态或错误代码问题;spring的数据存取异常不是jdbc特定的,因此你的dao不必绑定到jdbc(由于其可能抛出的异常)。
四. spring jdbc示例
在下面两个列表中,我们将使用前面用传统型jdbc实现的业务逻辑为例并且展示使用spring jdbc版本是多么容易。首先,我们从一个简单的接口开始。
package com.spring.jdbc;
import java.util.list;
public interface tasksdao {
public list gettasksnames();
}
接下来,我们提供针对于tasksdao接口的一种实现。
package com.spring.jdbc;
import java.util.iterator;
import java.util.list;
import java.sql.resultset;
import java.sql.sqlexception;
import javax.sql.datasource;
import org.springframework.context.applicationcontext;
import org.springframework.context.support.classpathxmlapplicationcontext;
import org.springframework.jdbc.core.jdbctemplate;
import org.springframework.jdbc.core.rowmapper;
import org.springframework.jdbc.core.rowmapperresultreader;
import org.springframework.jdbc.core.support.jdbcdaosupport;
public class tasksjdbcdao extends jdbcdaosupport
implements tasksdao {
public list gettasksnames() {
jdbctemplate jt = getjdbctemplate();
return jt.query("select taskname from tasks",
new rowmapperresultreader(new tasksrowmapper()));
}
class tasksrowmapper implements rowmapper {
public object maprow(resultset rs, int index)
throws sqlexception {
return rs.getstring(1);
}
}
public static void main(string[] args)throws exception {
applicationcontext ctx = new classpathxmlapplicationcontext("springconfig.xml");
datasource ds =(datasource) ctx.getbean("datasourcedbdirect");
tasksjdbcdao taskdao = new tasksjdbcdao();
taskdao.setdatasource(ds);
iterator tskiter = taskdao.gettasksnames().iterator();
while (tskiter.hasnext()) {
system.out.println(tskiter.next().tostring());
}
}
}
在上面的例子中,普通的和复杂的任务代码已经被移交到框架中。还应注意,借助于spring jdbc,我们如何利用控制反转(ioc)容器来提供一种datasource-我们仅把它注入到tasksjdbcdao对象中。
控制反转背后的概念通常被表达为"不要找我,让我找你好了"。ioc把一些任务移交到了框架中,并且脱离出了应用程序代码。不是让你的代码调用一个传统的类库,而是一个ioc框架调用你的代码。存在于许多api中的生命周期回调,例如相应于会话ejb的setsessioncontext()方法,正是展示了这种方法。
datasource必须被注入到这个类中(或者其超类中),这是通过setdatasource()方法实现的。所有配置细节都远离了业务逻辑或客户端代码;这增加你的应用程序的松耦合性并因此而提高了程序的可测试性和可维护性。作为选择,我们还能在jndi或servlet容器中建立一个datasource,并用编程方式来检索它,然后把它注入到dao对象中。下面是一个你可以使用的示例spring bean配置文件-springconfig.xml:
<?xml version="1.0" encoding="utf-8" ?>
<!doctype beans public "-//spring//dtd bean//en"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="datasourcedbdirect"
class="org.springframework.jdbc.datasource.drivermanagerdatasource"
destroy-method="close">
<property name="driverclassname" value="oracle.jdbc.driver.oracledriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
</beans>
这个文件指示spring bean容器实例化一个datasourcedbdirect bean-它基于org.springframework.jdbc.datasource.drivermanagerdatasource类创建。 1) 基于spring jdbc实现一个业务层
我们已经看到了一个简单的使用spring jdbc的例子,在这种情况下,它从spring beanfactory(控制反转容器)中得到极少的帮助。现在,我们将超越这个简单的例子。让我们来探讨一下如何基于spring jdbc实现业务服务。首先,让我们创建一个客户端-一个为终端用户提供输出的应用程序。该客户端使用了一个服务,一个遵守下面的service接口的业务服务:
package com.spring.jdbc;
import java.util.list;
public interface service {
public list gettasksnames();
public void settasksdao(tasksdao taskdao);
}
客户端需要存取一个业务服务对象。它将使用spring beancontainer来"抓住"这样的一个服务对象。客户端仅能针对接口编程并且依赖容器来提供一种实际的实现。而且,这个serviceimpl类必须实现所有的存在于业务服务接口中的方法。该代码看上去如下所示:
package com.spring.jdbc;
import java.util.list;
public class serviceimpl implements service{
tasksdao taskdao;
public void settasksdao(tasksdao taskdao)
{
this.taskdao=taskdao;
}
public list gettasksnames()
{
list tasks = taskdao.gettasksnames();
return tasks;
}
}
你应该已经注意到,该服务需要一个tasksjdbcdao。反过来,这个对象实现了tasksdao接口。因此,我们将通过beanfactory来把dao注入到该服务中。在此,我们碰巧有一个tasksjdbcdao类-bean工厂可以使用它来实现这一目的。然而,既然这个类派生于jdbcdaosupport,那么我们知道我们需要注入一个datasource或让bean工厂为我们注入该datasource。现在这个bean配置文件看上去如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<!doctype beans public "-//spring//dtd bean//en"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="datasourcedbdirect"
class="org.springframework.jdbc.datasource.drivermanagerdatasource"
destroy-method="close">
<property name="driverclassname" value="oracle.jdbc.driver.oracledriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<bean id="tasksdao" class="com.spring.jdbc.tasksjdbcdao">
<property name="datasource">
<ref local="datasourcedbdirect"/>
</property>
</bean>
<bean id="service" class="com.spring.jdbc.serviceimpl">
<property name="tasksdao">
<ref local="tasksdao"/>
</property>
</bean>
</beans>
我们看到服务bean使得tasksdao bean被注入-它反过来又使datasourcedbdirect对象被注入。当我们请求服务bean时,我们通过一个具有datasource的dao得到它。至此,一切就绪。因此,当客户端存取bean容器以得到服务对象时,会发生什么呢?该bean容器实例化并且注入一个datasource和一个tasksdao-在把服务返回到客户端之前。现在,我们的客户端变得相当简单了。它需要与beanfactory进行通讯,"抓住"一个服务对象并处理它:
package com.spring.jdbc;
import java.util.iterator;
import org.springframework.context.applicationcontext;
import org.springframework.context.support.classpathxmlapplicationcontext;
public class client extends runtimeexception {
public static void main(string[] args) throws exception {
applicationcontext ctx = new classpathxmlapplicationcontext("springconfig.xml");
service service = (service)ctx.getbean("service");
iterator tskiter = service.gettasksnames().iterator();
while (tskiter.hasnext()) {
system.out.println(tskiter.next().tostring());
}
}
}
你必须注意,在此client派生于runtimeexception异常类。spring抛出了runtimeexceptions而不是检查的异常-runtimeexceptions不应该被捕获。由于在你的代码中捕获所有异常是一种复杂的任务,所以spring开发者决定抛出runtimeexceptions以便实现如果你不捕获一个异常的话,那么你的应用程序将会中断而且用户会得到该应用程序异常。使用它们的第二个理由是,绝大多数异常都是不可恢复的,因此你的应用程序逻辑不能以任何方式来再次处理它们。 五. 另外的优点
除了上面描述的spring框架带给jdbc中的优点外,与你的jdbc应用程序一起使用spring框架还存在另外一些优点。这些优点包括:
· spring框架提供了
org.springframework.jdbc.support.nativejdbc.nativejdbcextractor接口和这个接口的一些实现(例如simplenativejdbcextractor)。对于经由一个oracle连接或resultset访问oracle特征的情况来说,这些内容是非常有用的。
· 对于创建oracle.sql.blob(二进制大型对象)和oracle.sql.clob(字符大型对象)实例来说,spring提供了类org.springframework.jdbc.support.lob.oraclelobhandler。
· spring提供的oraclesequencemaxvalueincrementer类提供了一个oracle序列的下一个值。它有效地提供了与你直接使用下列命令:"select somesequence.nextval from dual"(其中,somesequence是在oracle数据库中的你的序列的名字)所提供的一样的信息。这种方法的优点是,datafieldmaxvalueincrementer接口可以用于一个dao层次中而不必紧密地耦合于oracle特定的实现。
六. 结论
本文集中讨论了使用spring来编写更可维护的和更不易出现错误的jdbc代码。spring jdbc提供了一些优点,例如更为干净的代码,更好的异常与资源处理,并且能够集中于业务问题而不是复杂的任务代码。另外,值得注意的是,使用spring框架能够使用极少的代码就可以实现实质上与传统型jdbc相同的功能。