2023年6月21日发(作者:)

JdbcTemplate系列(⼀)----使⽤详解⼀、简介1.什么是JDBC模板(jdbcTemplate)JDBC--->Java Data Base Connectivity,java数据库连接,简单点说就是可以为多种关系数据库提供统⼀访问,由⼀组⽤java语⾔编写的类和接⼝组成。模板就是事先准备好的东西,你只需要去套⽤就可以,JDBCTemplate就是这样的模板,通过设置JDBCTemplate可以减少对数据库的繁琐操作,例如连接数据库,获得链接关闭,获得statement,resultset,preparedstatement这些等等。2.传统的JDBC与Spring JDBC区别2.1、传统的JDBC应⽤步骤:1.指定数据库连接参数2.打开数据库连接3.声明SQL语句4.预编译并执⾏SQL语句5.遍历查询结果(如果需要的话)6.处理每⼀次遍历操作7.处理抛出的任何异常8.处理事务9.关闭数据库连接JDBC的缺点就是太⿇烦了,不易编码,容易出错,不利于开发者把精⼒投⼊到业务上去。简化JDBC就是新技术的⽬标。象Spring,hibernate等都通过对JDBC进⾏封装,以达到简化开发的⽬的。但是这些技术在⾃⾝侧重上略有不同。如Hibernate主要进⾏Object/Relational Mapping。2.2、Spring JDBCSpring的JDBC: 节省代码,不管连接(Connection),不管事务,不管异常,不管关闭(() )如何封装的JDBC3.1、Spring JDBC包结构 Spring JDBC抽象框架由四个包构成: core、dataSource、object以及support。包由JdbcTemplate类以及相关的回调接⼝(callback interface)和类组成。urce包由⼀些⽤来简化DataSource访问的⼯具类,以及各种DataSource接⼝的简单实现(主要⽤于单元测试以及在J2EE容器之外使⽤JDBC)组成。⼯具类提供了⼀些静态⽅法,诸如通过JNDI获取数据连接以及在必要的情况下关闭这些连接。它⽀持绑定线程的连接,⽐如被⽤于DataSourceTransactionManager的连接。接下来,包由封装了查询、更新以及存储过程的类组成,这些类的对象都是线程安全并且可重复使⽤的。它们类似于JDO,与JDO的不同之处在于查询结果与数据库是“断开连接”的。它们是在包的基础上对JDBC更⾼层次的抽象。最后,t包提供了⼀些SQLException的转换类以及相关的⼯具类。在JDBC处理过程中抛出的异常将被转换成包中定义的异常。因此使⽤Spring JDBC进⾏开发将不需要处理JDBC或者特定的RDBMS才会抛出的异常。所有的异常都是unchecked exception,这样我们就可以对传递到调⽤者的异常进⾏有选择的捕获。mplateSpring对数据库的操作在jdbc上⾯做了深层次的封装,使⽤spring的注⼊功能,可以把DataSource注册到JdbcTemplate之中。JdbcTemplate位于中。其全限定命名为mplate。要使⽤这个包包含了⼀下事务和异常控制JdbcTemlate还需⼀个4.1、JdbcTemplate主要提供以下五类⽅法:execute⽅法:可以⽤于执⾏任何SQL语句,⼀般⽤于执⾏DDL语句;update⽅法及batchUpdate⽅法:update⽅法⽤于执⾏新增、修改、删除等语句;batchUpdate⽅法⽤于执⾏批处理相关语句;query⽅法及queryForXXX⽅法:⽤于执⾏查询相关语句;call⽅法:⽤于执⾏存储过程、函数相关语句。4.2、JdbcTemplate类⽀持的回调类:预编译语句及存储过程创建回调:⽤于根据JdbcTemplate提供的连接创建相应的语句; PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由⽤户使⽤该Conncetion创建相关的PreparedStatement; CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由⽤户使⽤该Conncetion创建相关的CallableStatement;预编译语句设值回调:⽤于给预编译语句相应参数设值; PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由⽤户来对相应的预编译语句相应参数设值; BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但⽤于批处理,需要指定批处理⼤⼩;⾃定义功能回调:提供给⽤户⼀个扩展点,⽤户可以在指定类型的扩展点执⾏任何数量需要的操作; ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,⽤户可在该Connection执⾏任何数量的操作; StatementCallback:通过回调获取JdbcTemplate提供的Statement,⽤户可以在该Statement执⾏任何数量的操作; PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,⽤户可以在该PreparedStatement执⾏任何数量的操作; CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,⽤户可以在该CallableStatement执⾏任何数量的操作;结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式; RowMapper:⽤于将结果集每⾏数据转换为需要的类型,⽤户需实现⽅法mapRow(ResultSet rs, int rowNum)来完成将每⾏数据转换为相应的类型。 RowCallbackHandler:⽤于处理ResultSet的每⼀⾏结果,⽤户需实现⽅法processRow(ResultSet rs)来完成处理,在该回调⽅法中⽆需执⾏(),该操作由JdbcTemplate来执⾏,⽤户只需按⾏获取数据然后处理即可。 ResultSetExtractor:⽤于结果集数据提取,⽤户需实现⽅法extractData(ResultSet rs)来处理结果集,⽤户必须处理整个结果集;4.3、配置JdbcTemplate要使⽤Jdbctemplate 对象来完成jdbc 操作。通常情况下,有三种种⽅式得到JdbcTemplate 对象。 第⼀种⽅式:我们可以在⾃⼰定义的DAO 实现类中注⼊⼀个DataSource 引⽤来完 成JdbcTemplate 的实例化。也就是它是从外部“注⼊” DataSource 到DAO 中,然后⾃⼰实例化JdbcTemplate,然后将DataSource 设置到JdbcTemplate 对象中。

第⼆种⽅式: 在 Spring 的 IoC 容器中配置⼀个 JdbcTemplate 的 bean,将 DataSource 注⼊进来,然后再把JdbcTemplate 注⼊到⾃定义DAO 中。

第三种⽅式: Spring 提供了 oSupport 类 , 这 个 类 中 定 义 了JdbcTemplate 属性,也定义了DataSource 属性,当设置DataSource 属性的时候,会创 建jdbcTemplate 的实例,所以我们⾃⼰编写的DAO 只需要继承JdbcDaoSupport 类, 然后注⼊DataSource 即可。提倡采⽤第三种⽅法。第⼀种:public class UserServiceImpl implements UserService {

private JdbcTemplate jdbcTemplate;

public JdbcTemplate getJdbcTemplate() {

return jdbcTemplate;

}

//注⼊⽅法1

public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {

mplate = jdbcTemplate;

}

//其它⽅法这⾥省略……

}

spring配置⽂件为:

第⼆种:public class UserServiceImpl implements UserService {

private JdbcTemplate jdbcTemplate;

//注⼊⽅法2

public void setDataSource(DataSource dataSource) {

mplate = new JdbcTemplate(dataSource);

}

//其它⽅法省略……

}

spring配置⽂件为:

第三种:继承JdbcDaoSupport,其内部有个JdbcTemplate ,需要注⼊DataSource 属性来实例化。public class UserDaoImpl extends JdbcDaoSupport implements UserDao {

@Override

public void save(User user) {

String sql = null;

cTemplate().update(sql);

}

//其它⽅法省略……

}

spring配置⽂件为:

⼆、详解1.利⽤JDBC核⼼类实现JDBC的基本操作和错误处理 1.1、JdbcTemplate类JdbcTemplate是core包的核⼼类。它替我们完成了资源的创建以及释放⼯作,从⽽简化了我们对JDBC的使⽤。它还可以帮助我们避免⼀些常见的错误,⽐如忘记关闭数据库连接。JdbcTemplate将完成JDBC核⼼处理流程,⽐如SQL语句的创建、执⾏,⽽把SQL语句的⽣成以及查询结果的提取⼯作留给我们的应⽤代码。它可以完成SQL查询、更新以及调⽤存储过程,可以对ResultSet进⾏遍历并加以提取。它还可以捕获JDBC异常并将其转换成包中定义的,通⽤的,信息更丰富的异常。使⽤JdbcTemplate进⾏编码只需要根据明确定义的⼀组契约来实现回调接⼝。PreparedStatementCreator回调接⼝通过给定的Connection创建⼀个PreparedStatement,包含SQL和任何相关的参数。CallableStatementCreateor实现同样的处理,只不过它创建的是CallableStatement。RowCallbackHandler接⼝则从数据集的每⼀⾏中提取值。我们可以在⼀个service实现类中通过传递⼀个DataSource引⽤来完成JdbcTemplate的实例化,也可以在applicationcontext中配置⼀个JdbcTemplate bean,来供service使⽤。需要注意的是DataSource在application context总是配制成⼀个bean,第⼀种情况下,DataSource bean将传递给service,第⼆种情况下DataSource bean传递给JdbcTemplate bean。因为JdbcTemplate使⽤回调接⼝和SQLExceptionTranslator接⼝作为参数,所以⼀般情况下没有必要通过继承JdbcTemplate来定义其⼦类。JdbcTemplate中使⽤的所有SQL将会以“DEBUG”级别记⼊⽇志(⼀般情况下⽇志的category是JdbcTemplate相应的全限定类名,不过如果需要对JdbcTemplate进⾏定制的话,可能是它的⼦类名)。1.2、NamedParameterJdbcTemplate类NamedParameterJdbcTemplate类增加了在SQL语句中使⽤命名参数的⽀持。在此之前,在传统的SQL语句中,参数都是⽤'?'占位符来表⽰的。 NamedParameterJdbcTemplate类内部封装了⼀个普通的JdbcTemplate,并作为其代理来完成⼤部分⼯作。下⾯的内容主要针对NamedParameterJdbcTemplate与JdbcTemplate的不同之处来加以说明,即如何在SQL语句中使⽤命名参数。通过下⾯的例⼦我们可以更好地了解NamedParameterJdbcTemplate的使⽤模式// some JDBC-backed public int countOfActorsByFirstName(String firstName) { String sql = "select count(0) from T_ACTOR where first_name = :first_name"; NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(aSource()); SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName); return orInt(sql, namedParameters);}如果你喜欢的话,也可以使⽤基于Map风格的名值对将命名参数传递给NamedParameterJdbcTemplate(NamedParameterJdbcTemplate实现了NamedParameterJdbcOperations接⼝,剩下的⼯作将由调⽤该接⼝的相应⽅法来完成):

// some JDBC-backed public int countOfActorsByFirstName(String firstName) { String sql = "select count(0) from T_ACTOR where first_name = :first_name"; NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(aSource()); Map namedParameters = new HashMap(); ("first_name", firstName); return orInt(sql, namedParameters);}SqlParameterSource接⼝的另⼀个实现--BeanPropertySqlParameterSource为我们提供了更有趣的功能。该类包装⼀个类似JavaBean的对象,所需要的命名参数值将由包装对象提供,下⾯我们使⽤⼀个例⼦来更清楚地说明它的⽤法。// some public class Actor { private Long id; private String firstName; private String lastName;

public String getFirstName() { return ame; }

public String getLastName() { return me; }

public Long getId() { return ; }

// }// some JDBC-backed public int countOfActors(Actor exampleActor) { // notice how the named parameters match the properties of the above 'Actor' class String sql = "select count(0) from T_ACTOR where first_name = :firstName and last_name = :lastName"; NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(aSource()); SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor); return orInt(sql, namedParameters);}注意:NamedParameterJdbcTemplate类是线程安全的,该类的最佳使⽤⽅式不是每次操作的时候实例化⼀个新的NamedParameterJdbcTemplate,⽽是针对每个DataSource只配置⼀个NamedParameterJdbcTemplate实例(⽐如在Spring IoC容器中使⽤Spring IoC来进⾏配置),然后在那些使⽤该类的DAO中共享该实例。1.3、SimpleJdbcTemplate类SimpleJdbcTemplate 类利⽤Java 5的语法特性带来的好处可以通过⼀个例⼦来说明。在下⾯的代码⽚断中我们⾸先使⽤标准的JdbcTemplate进⾏数据访问,接下来使⽤SimpleJdbcTemplate做同样的事情。// public Actor findActor(long id) { String sql = "select id, first_name, last_name from T_ACTOR where id = ?"; RowMapper mapper = new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Actor actor = new Actor(); (g(f(g("id")))); stName(ing("first_name")); tName(ing("last_name")); return actor; } }; // normally this would be dependency injected JdbcTemplate jdbcTemplate = new JdbcTemplate(aSource()); // notice the cast, and the wrapping up of the 'id' argument // in an array, and the boxing of the 'id' argument as a reference type return (Actor) orObject(sql, mapper, new Object[] {f(id)});}下⾯是同⼀⽅法的另⼀种实现,惟⼀不同之处是我们使⽤了SimpleJdbcTemplate,这样代码显得更加清晰。 // public Actor findActor(long id) { String sql = "select id, first_name, last_name from T_ACTOR where id = ?";

ParameterizedRowMapper mapper = new ParameterizedRowMapper() {

// notice the return type with respect to Java 5 covariant return types public Actor mapRow(ResultSet rs, int rowNum) throws SQLException { Actor actor = new Actor(); (g("id")); stName(ing("first_name")); tName(ing("last_name")); return actor; } };

// again, normally this would be dependency injected SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(aSource()); return orObject(sql, mapper, id);}1.4、DataSource接⼝为了从数据库中取得数据,我们⾸先需要获取⼀个数据库连接。 Spring通过DataSource对象来完成这个⼯作。DataSource是JDBC规范的⼀部分, 它被视为⼀个通⽤的数据库连接⼯⼚。通过使⽤DataSource, Container或Framework可以将连接池以及事务管理的细节从应⽤代码中分离出来。 作为⼀个开发⼈员,在开发和测试产品的过程中,你可能需要知道连接数据库的细节。 但在产品实施时,你不需要知道这些细节。通常数据库管理员会帮你设置好数据源。在使⽤Spring JDBC时,你既可以通过JNDI获得数据源,也可以⾃⾏配置数据源(使⽤Spring提供的DataSource实现类)。使⽤后者可以更⽅便的脱离Web容器来进⾏单元测试。 这⾥我们将使⽤DriverManagerDataSource,不过DataSource有多种实现, 后⾯我们会讲到。使⽤DriverManagerDataSource和你以前获取⼀个JDBC连接 的做法没什么两样。你⾸先必须指定JDBC驱动程序的全限定名,这样DriverManager 才能加载JDBC驱动类,接着你必须提供⼀个url(因JDBC驱动⽽异,为了保证设置正确请参考相关JDBC驱动的⽂档), 最后你必须提供⼀个⽤户连接数据库的⽤户名和密码。下⾯我们将通过⼀个例⼦来说明如何配置⼀个 DriverManagerDataSource:DriverManagerDataSource dataSource = new DriverManagerDataSource();verClassName("iver");("jdbc:hsqldb:hsql://localhost:");rname("sa");sword("");1.5、SQLExceptionTranslator接⼝SQLErrorCodeSQLExceptionTranslator是SQLExceptionTranslator的默认实现。 该实现使⽤指定数据库⼚商的errorcode,⽐采⽤SQLState更精确。 转换过程基于⼀个JavaBean(类型为SQLErrorCodes)中的error code。 这个JavaBean由SQLErrorCodesFactory⼯⼚类创建,其中的内容来⾃于 ""配置⽂件。该⽂件中的数据库⼚商代码基于Database MetaData信息中的 DatabaseProductName,从⽽配合当前数据库的使⽤。SQLErrorCodeSQLExceptionTranslator使⽤以下的匹配规则:● ⾸先检查是否存在完成定制转换的⼦类实现。通常SQLErrorCodeSQLExceptionTranslator 这个类可以作为⼀个具体类使⽤,不需要进⾏定制,那么这个规则将不适⽤。● 接着将SQLException的error code与错误代码集中的error code进⾏匹配。 默认情况下错误代码集将从SQLErrorCodesFactory取得。 错误代码集来⾃classpath下的⽂件, 它们将与数据库metadata信息中的database name进⾏映射。● 如果仍然⽆法匹配,最后将调⽤fallbackTranslator属性的translate⽅法,SQLStateSQLExceptionTranslator类实例是默认的fallbackTranslator。SQLErrorCodeSQLExceptionTranslator可以采⽤下⾯的⽅式进⾏扩展:public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) { if (orCode() == -12345) { return new DeadlockLoserDataAccessException(task, sqlex); } return null; }}在上⾯的这个例⼦中,error code为'-12345'的SQLException 将采⽤该转换器进⾏转换,⽽其他的error code将由默认的转换器进⾏转换。 为了使⽤该转换器,必须将其作为参数传递给JdbcTemplate类 的setExceptionTranslator⽅法,并在需要使⽤这个转换器器的数据 存取操作中使⽤该JdbcTemplate。 下⾯的例⼦演⽰了如何使⽤该定制转换器:

// create a JdbcTemplate and set data sourceJdbcTemplate jt = new JdbcTemplate();aSource(dataSource);// create a custom translator and set the DataSource for the default translation lookupMySQLErrorCodesTransalator tr = new MySQLErrorCodesTransalator();aSource(dataSource);eptionTranslator(tr);// use the JdbcTemplate for this SqlUpdateSqlUpdate su = new SqlUpdate();cTemplate(jt);("update orders set shipping_charge = shipping_charge * 1.05");e();();在上⾯的定制转换器中,我们给它注⼊了⼀个数据源,因为我们仍然需要 使⽤默认的转换器从中获取错误代码集。

1.6、执⾏SQL语句我们仅需要⾮常少的代码就可以达到执⾏SQL语句的⽬的,⼀旦获得⼀个 DataSource和⼀个JdbcTemplate, 我们就可以使⽤JdbcTemplate提供的丰富功能实现我们的操作。 下⾯的例⼦使⽤了极少的代码完成创建⼀张表的⼯作。import urce;import mplate;public class ExecuteAStatement {private JdbcTemplate jt;private DataSource dataSource; public void doExecute() { jt = new JdbcTemplate(dataSource); e("create table mytable (id integer, name varchar(100))"); } public void setDataSource(DataSource dataSource) { urce = dataSource; }}1.7、执⾏查询除了execute⽅法之外,JdbcTemplate还提供了⼤量的查询⽅法。 在这些查询⽅法中,有很⼤⼀部分是⽤来查询单值的。⽐如返回⼀个汇总(count)结果 或者从返回⾏结果中取得指定列的值。这时我们可以使⽤queryForInt(..)、queryForLong(..)或者queryForObject(..)⽅法。 queryForObject⽅法⽤来将返回的JDBC类型对象转换成指定的Java对象,如果类型转换失败将抛出 InvalidDataAccessApiUsageException异常。 下⾯的例⼦演⽰了两个查询的⽤法,⼀个返回int值,另⼀个返回 String。import urce;import mplate;public class RunAQuery { private JdbcTemplate jt; private DataSource dataSource; public int getCount() { jt = new JdbcTemplate(dataSource); int count = orInt("select count(*) from mytable"); return count; } public String getName() { jt = new JdbcTemplate(dataSource); String name = (String) orObject("select name from mytable", ); return name; } public void setDataSource(DataSource dataSource) { urce = dataSource; }}除了返回单值的查询⽅法,JdbcTemplate还提供了⼀组返回List结果 的⽅法。List中的每⼀项对应查询返回结果中的⼀⾏。其中最简单的是queryForList⽅法, 该⽅法将返回⼀个List,该List中的每⼀条 记录是⼀个Map对象,对应应数据库中某⼀⾏;⽽该Map 中的每⼀项对应该数据库⾏中的某⼀列值。下⾯的代码⽚断接着上⾯的例⼦演⽰了如何⽤该⽅法返回表中 所有记录:public List getList() { jt = new JdbcTemplate(dataSource); List rows = orList("select * from mytable"); return rows;}返回的结果集类似下⾯这种形式:[{name=Bob, id=1}, {name=Mary, id=2}]1.8、更新数据库JdbcTemplate还提供了⼀些更新数据库的⽅法。 在下⾯的例⼦中,我们根据给定的主键值对指定的列进⾏更新。 例⼦中的SQL语句中使⽤了“?”占位符来接受参数(这种做法在更新和查询SQL语句中很常见)。 传递的参数值位于⼀个对象数组中(基本类型需要被包装成其对应的对象类型)。import urce;import mplate;public class ExecuteAnUpdate { private JdbcTemplate jt; private DataSource dataSource;

public void setName(int id, String name) { jt = new JdbcTemplate(dataSource); ("update mytable set name = ? where id = ?", new Object[] {name, new Integer(id)}); } public void setDataSource(DataSource dataSource) { urce = dataSource; }}2.控制数据库连接2.1、DataSourceUtils类DataSourceUtils作为⼀个帮助类提供易⽤且强⼤的数据库访问能⼒, 我们可以使⽤该类提供的静态⽅法从JNDI获取数据库连接以及在必要的时候关闭之。 它提供⽀持线程绑定的数据库连接(⽐如使⽤DataSourceTransactionManager 的时候,将把数据库连接绑定到当前的线程上)。注:getDataSourceFromJndi(..)⽅法主要⽤于那些没有使⽤bean factory 或者application context的场合。如果使⽤application context,那么最好是在 JndiObjectFactoryBean中配置bean或者直接使⽤ JdbcTemplate实例。JndiObjectFactoryBean 能够通过JNDI获取DataSource并将 DataSource作为引⽤参数传递给其他bean。 这样,在不同的DataSource之间切换只需要修改配置⽂件即可, 甚⾄我们可以⽤⼀个⾮JNDI的DataSource来替换 FactoryBean定义!2.2、SmartDataSource接⼝SmartDataSource是DataSource 接⼝的⼀个扩展,⽤来提供数据库连接。使⽤该接⼝的类在指定的操作之后可以检查是否需要关闭连接。 该接⼝在某些情况下⾮常有⽤,⽐如有些情况需要重⽤数据库连接。2.3、AbstractDataSource类AbstractDataSource是⼀个实现了DataSource 接⼝的abstract基类。它实现了DataSource接⼝的 ⼀些⽆关痛痒的⽅法,如果你需要实现⾃⼰的DataSource,那么继承 该类是个好主意。2.4、SingleConnectionDataSource类SingleConnectionDataSource是SmartDataSource接⼝ 的⼀个实现,其内部包装了⼀个单连接。该连接在使⽤之后将不会关闭,很显然它不能在多线程 的环境下使⽤。当客户端代码调⽤close⽅法的时候,如果它总是假设数据库连接来⾃连接池(就像使⽤持久化⼯具时⼀样), 你应该将suppressClose设置为true。 这样,通过该类获取的将是代理连接(禁⽌关闭)⽽不是原有的物理连接。 需要注意的是,我们不能把使⽤该类获取的数据库连接造型(cast)为Oracle Connection之类的本地数据库连接。SingleConnectionDataSource主要在测试的时候使⽤。 它使得测试代码很容易脱离应⽤服务器⽽在⼀个简单的JNDI环境下运⾏。 与DriverManagerDataSource不同的是,它始终只会使⽤同⼀个数据库连接, 从⽽避免每次建⽴物理连接的开销。

2.5、DriverManagerDataSource类DriverManagerDataSource类实现了 SmartDataSource接⼝。在中可以使⽤ beanproperties来设置JDBC Driver属性,该类每次返回的都是⼀个新的连接。该类主要在测试以及脱离J2EE容器的独⽴环境中使⽤。它既可以⽤来在application context中作为⼀个 DataSourcebean,也可以在简单的JNDI环境下使⽤。 由于()仅仅只是简单的关闭数据库连接,因此任何能够获取DataSource的持久化代码都能很好的⼯作。不过使⽤JavaBean风格的连接池 (⽐如commons-dbcp)也并⾮难事。即使是在测试环境下,使⽤连接池也是⼀种⽐使⽤ DriverManagerDataSource更好的做法。2.6、TransactionAwareDataSourceProxy类TransactionAwareDataSourceProxy作为⽬标DataSource的⼀个代理, 在对⽬标DataSource包装的同时,还增加了Spring的事务管理能⼒, 在这⼀点上,这个类的功能⾮常像J2EE服务器所提供的事务化的JNDI DataSource。注意:该类⼏乎很少被⽤到,除⾮现有代码在被调⽤的时候需要⼀个标准的 JDBC DataSource接⼝实现作为参数。 这种情况下,这个类可以使现有代码参与Spring的事务管理。通常最好的做法是使⽤更⾼层的抽象 来对数据源进⾏管理,⽐如JdbcTemplate和DataSourceUtils等等。2.7、DataSourceTransactionManager类DataSourceTransactionManager类是 PlatformTransactionManager接⼝的⼀个实现,⽤于处理单JDBC数据源。 它将从指定DataSource取得的JDBC连接绑定到当前线程,因此它也⽀持了每个数据源对应到⼀个线程。我们推荐在应⽤代码中使⽤nection(DataSource)来获取 JDBC连接,⽽不是使⽤J2EE标准的nection。因为前者将抛出 unchecked的异常,⽽不是checked的SQLException异常。Spring Framework中所有的类(⽐如 JdbcTemplate)都采⽤这种做法。如果不需要和这个DataSourceTransactionManager类⼀起使⽤,DataSourceUtils 提供的功能跟⼀般的数据库连接策略没有什么两样,因此它可以在任何场景下使⽤。DataSourceTransactionManager类⽀持定制隔离级别,以及对SQL语句查询超时的设定。 为了⽀持后者,应⽤代码必须使⽤JdbcTemplate或者在每次创建SQL语句时调⽤ ransactionTimeout⽅法。在使⽤单个数据源的情形下,你可以⽤DataSourceTransactionManager来替代JtaTransactionManager, 因为DataSourceTransactionManager不需要容器⽀持JTA。如果你使⽤nection(DataSource)来获取 JDBC连接,⼆者之间的切换只需要更改⼀些配置。最后需要注意的⼀点就是JtaTransactionManager不⽀持隔离级别的定制!3.⽤Java对象来表达JDBC操作包下的类允许⽤户以更加 ⾯向对象的⽅式去访问数据库。⽐如说,⽤户可以执⾏查询并返回⼀个list, 该list作为⼀个结果集将把从数据库中取出的列数据映射到业务对象的属性上。 ⽤户也可以执⾏存储过程,以及运⾏更新、删除以及插⼊SQL语句。注意:必须说明的⼀点就是,这仅仅只是⼀种观点⽽已, 如果你认为你可以从直接使⽤RDBMS操作类中获取⼀些额外的好处, 你不妨根据⾃⼰的需要和喜好进⾏不同的选择。3.1、SqlQuery类SqlQuery是⼀个可重⽤、线程安全的类,它封装了⼀个SQL查询。 其⼦类必须实现newResultReader()⽅法,该⽅法⽤来在遍历 ResultSet的时候能使⽤⼀个类来保存结果。 我们很少需要直接使⽤SqlQuery,因为其⼦类 MappingSqlQuery作为⼀个更加易⽤的实现能够将结果集中的⾏映射为Java对象。 SqlQuery还有另外两个扩展分别是MappingSqlQueryWithParameters和UpdatableSqlQuery。3.2、MappingSqlQuery类MappingSqlQuery是⼀个可重⽤的查询抽象类,其具体类必须实现 mapRow(ResultSet, int)抽象⽅法来将结果集中的每⼀⾏转换成Java对象。在SqlQuery的各种实现中, MappingSqlQuery是最常⽤也是最容易使⽤的⼀个。下⾯这个例⼦演⽰了⼀个定制查询,它将从客户表中取得的数据映射到⼀个 Customer类实例。private class CustomerMappingQuery extends MappingSqlQuery { public CustomerMappingQuery(DataSource ds) { super(ds, "SELECT id, name FROM customer WHERE id = ?"); eParameter(new SqlParameter("id", R)); compile(); }

public Object mapRow(ResultSet rs, int rowNumber) throws SQLException { Customer cust = new Customer(); ((Integer) ect("id")); e(ing("name")); return cust; }}在上⾯的例⼦中,我们为⽤户查询提供了⼀个构造函数并为构造函数传递了⼀个 DataSource参数。在构造函数⾥⾯我们把DataSource和⼀个⽤来返回查询结果的SQL语句作为参数 调⽤⽗类的构造函数。SQL语句将被⽤于⽣成⼀个PreparedStatement对象, 因此它可以包含占位符来传递参数。⽽每⼀个SQL语句的参数必须通过调⽤declareParameter⽅法来进⾏声明,该⽅法需要⼀个 SqlParameter(封装了⼀个字段名字和⼀个 中定义的JDBC类型)对象作为参数。 所有参数定义完之后,我们调⽤compile()⽅法来对SQL语句进⾏预编译。下⾯让我们看看该定制查询初始化并执⾏的代码:

public Customer getCustomer(Integer id) { CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource); Object[] parms = new Object[1]; parms[0] = id; List customers = e(parms);

if (() > 0) { return (Customer) (0); }else { return null; }}在上⾯的例⼦中,getCustomer⽅法通过传递惟⼀参数id来返回⼀个客户对象。 该⽅法内部在创建CustomerMappingQuery实例之后, 我们创建了⼀个对象数组⽤来包含要传递的查询参数。这⾥我们只有唯⼀的⼀个Integer参数。执⾏CustomerMappingQuery的 execute⽅法之后,我们得到了⼀个List,该List中包含⼀个 Customer对象,如果有对象满⾜查询条件的话。

3.3、SqlUpdate类SqlUpdate类封装了⼀个可重复使⽤的SQL更新操作。 跟所有RdbmsOperation类⼀样,SqlUpdate可以在SQL中定义参数。该类提供了⼀系列update()⽅法,就像SqlQuery提供的⼀系列execute()⽅法⼀样。SqlUpdate是⼀个具体的类。通过在SQL语句中定义参数,这个类可以⽀持 不同的更新⽅法,我们⼀般不需要通过继承来实现定制。import Types;import Parameter;import Procedure;import urce;import p;import ;import ;import urce;import ameter;import ate;public class UpdateCreditRating extends SqlUpdate { public UpdateCreditRating(DataSource ds) { setDataSource(ds); setSql("update customer set credit_rating = ? where id = ?"); declareParameter(new SqlParameter(C)); declareParameter(new SqlParameter(C)); compile(); } /** * @param id for the Customer to be updated * @param rating the new value for credit rating * @return number of rows updated */ public int run(int id, int rating) { Object[] params = new Object[] { new Integer(rating), new Integer(id)}; return update(params); }}3.4、StoredProcedure类StoredProcedure类是⼀个抽象基类,它是对RDBMS存储过程的⼀种抽象。 该类提供了多种execute(..)⽅法,不过这些⽅法的访问类型都是protected的。从⽗类继承的sql属性⽤来指定RDBMS存储过程的名字。 尽管该类提供了许多必须在JDBC3.0下使⽤的功能,但是我们更关注的是JDBC 3.0中引⼊的命名参数特性。下⾯的程序演⽰了如何调⽤Oracle中的sysdate()函数。 这⾥我们创建了⼀个继承StoredProcedure的⼦类,虽然它没有输⼊参数, 但是我必须通过使⽤SqlOutParameter来声明⼀个⽇期类型的输出参数。 execute()⽅法将返回⼀个map,map中的每个entry是⼀个⽤参数名作key, 以输出参数为value的名值对。import ;import p;import or;import ;import urce;import Parameter;import urce.*;import Procedure;public class TestStoredProcedure { public static void main(String[] args) { TestStoredProcedure t = new TestStoredProcedure(); (); n("Done!"); } void test() { DriverManagerDataSource ds = new DriverManagerDataSource(); verClassName("Driver"); ("jdbc:oracle:thin:@localhost:1521:mydb"); rname("scott"); sword("tiger"); MyStoredProcedure sproc = new MyStoredProcedure(ds); Map results = e(); printMap(results); } private class MyStoredProcedure extends StoredProcedure { private static final String SQL = "sysdate"; public MyStoredProcedure(DataSource ds) { setDataSource(ds); setFunction(true); setSql(SQL); declareParameter(new SqlOutParameter("date", )); compile(); } public Map execute() { // the 'sysdate' sproc has no input parameters, so an empty Map return execute(new HashMap()); } } private static void printMap(Map results) { for (Iterator it = et().iterator(); t(); ) { n(()); } }}下⾯是StoredProcedure的另⼀个例⼦,它使⽤了两个Oracle游标类型的输出参数。 import Types;import Parameter;import Procedure;import urce;import p;import ;public class TitlesAndGenresStoredProcedure extends StoredProcedure { private static final String SPROC_NAME = "AllTitlesAndGenres"; public TitlesAndGenresStoredProcedure(DataSource dataSource) { super(dataSource, SPROC_NAME); declareParameter(new SqlOutParameter("titles", , new TitleMapper())); declareParameter(new SqlOutParameter("genres", , new GenreMapper())); compile(); } public Map execute() { // again, this sproc has no input parameters, so an empty Map return e(new HashMap()); }}值得注意的是TitlesAndGenresStoredProcedure构造函数中 declareParameter(..)的SqlOutParameter参数, 该参数使⽤了RowMapper接⼝的实现。 这是⼀种⾮常⽅便⽽强⼤的重⽤⽅式。 下⾯我们来看⼀下RowMapper的两个具体实现。⾸先是TitleMapper类,它简单的把ResultSet中的每⼀⾏映射为⼀个Title Domain Object。

import ;import per;import Set;import eption;public final class TitleMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Title title = new Title(); (g("id")); e(ing("name")); return title; }}另⼀个是GenreMapper类,也是⾮常简单的将ResultSet中的每⼀⾏映射为⼀个Genre Domain Object。

import per;import Set;import eption;import ;public final class GenreMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { return new Genre(ing("name")); }}如果你需要给存储过程传输⼊参数(这些输⼊参数是在RDBMS存储过程中定义好了的), 则需要提供⼀个指定类型的execute(..)⽅法, 该⽅法将调⽤基类的protected execute(Map parameters)⽅法。 例如:

public class TitlesAfterDateStoredProcedure extends StoredProcedure { private static final String SPROC_NAME = "TitlesAfterDate"; private static final String CUTOFF_DATE_PARAM = "cutoffDate";

public TitlesAfterDateStoredProcedure(DataSource dataSource) { super(dataSource, SPROC_NAME); declaraParameter(new SqlParameter(CUTOFF_DATE_PARAM, ); declareParameter(new SqlOutParameter("titles", , new TitleMapper())); compile(); } public Map execute(Date cutoffDate) { Map inputs = new HashMap(); (CUTOFF_DATE_PARAM, cutoffDate); return e(inputs); }}3.5、SqlFunction类SqlFunction RDBMS操作类封装了⼀个SQL“函数”包装器(wrapper), 该包装器适⽤于查询并返回⼀个单⾏结果集。默认返回的是⼀个int值, 不过我们可以采⽤类似JdbcTemplate中的queryForXxx 做法⾃⼰实现来返回其它类型。SqlFunction优势在于我们不必创建 JdbcTemplate,这些它都在内部替我们做了。该类的主要⽤途是调⽤SQL函数来返回⼀个单值的结果集,⽐如类似“select user()”、 “select sysdate fromdual”的查询。如果需要调⽤更复杂的存储函数, 可以使⽤StoredProcedure或SqlCall。SqlFunction是⼀个具体类,通常我们不需要它的⼦类。 其⽤法是创建该类的实例,然后声明SQL语句以及参数就可以调⽤相关的run⽅法去多次执⾏函数。 下⾯的例⼦⽤来返回指定表的记录⾏数:public int countRows() { SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable"); e(); return ();}

2023年6月21日发(作者:)

JdbcTemplate系列(⼀)----使⽤详解⼀、简介1.什么是JDBC模板(jdbcTemplate)JDBC--->Java Data Base Connectivity,java数据库连接,简单点说就是可以为多种关系数据库提供统⼀访问,由⼀组⽤java语⾔编写的类和接⼝组成。模板就是事先准备好的东西,你只需要去套⽤就可以,JDBCTemplate就是这样的模板,通过设置JDBCTemplate可以减少对数据库的繁琐操作,例如连接数据库,获得链接关闭,获得statement,resultset,preparedstatement这些等等。2.传统的JDBC与Spring JDBC区别2.1、传统的JDBC应⽤步骤:1.指定数据库连接参数2.打开数据库连接3.声明SQL语句4.预编译并执⾏SQL语句5.遍历查询结果(如果需要的话)6.处理每⼀次遍历操作7.处理抛出的任何异常8.处理事务9.关闭数据库连接JDBC的缺点就是太⿇烦了,不易编码,容易出错,不利于开发者把精⼒投⼊到业务上去。简化JDBC就是新技术的⽬标。象Spring,hibernate等都通过对JDBC进⾏封装,以达到简化开发的⽬的。但是这些技术在⾃⾝侧重上略有不同。如Hibernate主要进⾏Object/Relational Mapping。2.2、Spring JDBCSpring的JDBC: 节省代码,不管连接(Connection),不管事务,不管异常,不管关闭(() )如何封装的JDBC3.1、Spring JDBC包结构 Spring JDBC抽象框架由四个包构成: core、dataSource、object以及support。包由JdbcTemplate类以及相关的回调接⼝(callback interface)和类组成。urce包由⼀些⽤来简化DataSource访问的⼯具类,以及各种DataSource接⼝的简单实现(主要⽤于单元测试以及在J2EE容器之外使⽤JDBC)组成。⼯具类提供了⼀些静态⽅法,诸如通过JNDI获取数据连接以及在必要的情况下关闭这些连接。它⽀持绑定线程的连接,⽐如被⽤于DataSourceTransactionManager的连接。接下来,包由封装了查询、更新以及存储过程的类组成,这些类的对象都是线程安全并且可重复使⽤的。它们类似于JDO,与JDO的不同之处在于查询结果与数据库是“断开连接”的。它们是在包的基础上对JDBC更⾼层次的抽象。最后,t包提供了⼀些SQLException的转换类以及相关的⼯具类。在JDBC处理过程中抛出的异常将被转换成包中定义的异常。因此使⽤Spring JDBC进⾏开发将不需要处理JDBC或者特定的RDBMS才会抛出的异常。所有的异常都是unchecked exception,这样我们就可以对传递到调⽤者的异常进⾏有选择的捕获。mplateSpring对数据库的操作在jdbc上⾯做了深层次的封装,使⽤spring的注⼊功能,可以把DataSource注册到JdbcTemplate之中。JdbcTemplate位于中。其全限定命名为mplate。要使⽤这个包包含了⼀下事务和异常控制JdbcTemlate还需⼀个4.1、JdbcTemplate主要提供以下五类⽅法:execute⽅法:可以⽤于执⾏任何SQL语句,⼀般⽤于执⾏DDL语句;update⽅法及batchUpdate⽅法:update⽅法⽤于执⾏新增、修改、删除等语句;batchUpdate⽅法⽤于执⾏批处理相关语句;query⽅法及queryForXXX⽅法:⽤于执⾏查询相关语句;call⽅法:⽤于执⾏存储过程、函数相关语句。4.2、JdbcTemplate类⽀持的回调类:预编译语句及存储过程创建回调:⽤于根据JdbcTemplate提供的连接创建相应的语句; PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由⽤户使⽤该Conncetion创建相关的PreparedStatement; CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由⽤户使⽤该Conncetion创建相关的CallableStatement;预编译语句设值回调:⽤于给预编译语句相应参数设值; PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由⽤户来对相应的预编译语句相应参数设值; BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但⽤于批处理,需要指定批处理⼤⼩;⾃定义功能回调:提供给⽤户⼀个扩展点,⽤户可以在指定类型的扩展点执⾏任何数量需要的操作; ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,⽤户可在该Connection执⾏任何数量的操作; StatementCallback:通过回调获取JdbcTemplate提供的Statement,⽤户可以在该Statement执⾏任何数量的操作; PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,⽤户可以在该PreparedStatement执⾏任何数量的操作; CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,⽤户可以在该CallableStatement执⾏任何数量的操作;结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式; RowMapper:⽤于将结果集每⾏数据转换为需要的类型,⽤户需实现⽅法mapRow(ResultSet rs, int rowNum)来完成将每⾏数据转换为相应的类型。 RowCallbackHandler:⽤于处理ResultSet的每⼀⾏结果,⽤户需实现⽅法processRow(ResultSet rs)来完成处理,在该回调⽅法中⽆需执⾏(),该操作由JdbcTemplate来执⾏,⽤户只需按⾏获取数据然后处理即可。 ResultSetExtractor:⽤于结果集数据提取,⽤户需实现⽅法extractData(ResultSet rs)来处理结果集,⽤户必须处理整个结果集;4.3、配置JdbcTemplate要使⽤Jdbctemplate 对象来完成jdbc 操作。通常情况下,有三种种⽅式得到JdbcTemplate 对象。 第⼀种⽅式:我们可以在⾃⼰定义的DAO 实现类中注⼊⼀个DataSource 引⽤来完 成JdbcTemplate 的实例化。也就是它是从外部“注⼊” DataSource 到DAO 中,然后⾃⼰实例化JdbcTemplate,然后将DataSource 设置到JdbcTemplate 对象中。

第⼆种⽅式: 在 Spring 的 IoC 容器中配置⼀个 JdbcTemplate 的 bean,将 DataSource 注⼊进来,然后再把JdbcTemplate 注⼊到⾃定义DAO 中。

第三种⽅式: Spring 提供了 oSupport 类 , 这 个 类 中 定 义 了JdbcTemplate 属性,也定义了DataSource 属性,当设置DataSource 属性的时候,会创 建jdbcTemplate 的实例,所以我们⾃⼰编写的DAO 只需要继承JdbcDaoSupport 类, 然后注⼊DataSource 即可。提倡采⽤第三种⽅法。第⼀种:public class UserServiceImpl implements UserService {

private JdbcTemplate jdbcTemplate;

public JdbcTemplate getJdbcTemplate() {

return jdbcTemplate;

}

//注⼊⽅法1

public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {

mplate = jdbcTemplate;

}

//其它⽅法这⾥省略……

}

spring配置⽂件为:

第⼆种:public class UserServiceImpl implements UserService {

private JdbcTemplate jdbcTemplate;

//注⼊⽅法2

public void setDataSource(DataSource dataSource) {

mplate = new JdbcTemplate(dataSource);

}

//其它⽅法省略……

}

spring配置⽂件为:

第三种:继承JdbcDaoSupport,其内部有个JdbcTemplate ,需要注⼊DataSource 属性来实例化。public class UserDaoImpl extends JdbcDaoSupport implements UserDao {

@Override

public void save(User user) {

String sql = null;

cTemplate().update(sql);

}

//其它⽅法省略……

}

spring配置⽂件为:

⼆、详解1.利⽤JDBC核⼼类实现JDBC的基本操作和错误处理 1.1、JdbcTemplate类JdbcTemplate是core包的核⼼类。它替我们完成了资源的创建以及释放⼯作,从⽽简化了我们对JDBC的使⽤。它还可以帮助我们避免⼀些常见的错误,⽐如忘记关闭数据库连接。JdbcTemplate将完成JDBC核⼼处理流程,⽐如SQL语句的创建、执⾏,⽽把SQL语句的⽣成以及查询结果的提取⼯作留给我们的应⽤代码。它可以完成SQL查询、更新以及调⽤存储过程,可以对ResultSet进⾏遍历并加以提取。它还可以捕获JDBC异常并将其转换成包中定义的,通⽤的,信息更丰富的异常。使⽤JdbcTemplate进⾏编码只需要根据明确定义的⼀组契约来实现回调接⼝。PreparedStatementCreator回调接⼝通过给定的Connection创建⼀个PreparedStatement,包含SQL和任何相关的参数。CallableStatementCreateor实现同样的处理,只不过它创建的是CallableStatement。RowCallbackHandler接⼝则从数据集的每⼀⾏中提取值。我们可以在⼀个service实现类中通过传递⼀个DataSource引⽤来完成JdbcTemplate的实例化,也可以在applicationcontext中配置⼀个JdbcTemplate bean,来供service使⽤。需要注意的是DataSource在application context总是配制成⼀个bean,第⼀种情况下,DataSource bean将传递给service,第⼆种情况下DataSource bean传递给JdbcTemplate bean。因为JdbcTemplate使⽤回调接⼝和SQLExceptionTranslator接⼝作为参数,所以⼀般情况下没有必要通过继承JdbcTemplate来定义其⼦类。JdbcTemplate中使⽤的所有SQL将会以“DEBUG”级别记⼊⽇志(⼀般情况下⽇志的category是JdbcTemplate相应的全限定类名,不过如果需要对JdbcTemplate进⾏定制的话,可能是它的⼦类名)。1.2、NamedParameterJdbcTemplate类NamedParameterJdbcTemplate类增加了在SQL语句中使⽤命名参数的⽀持。在此之前,在传统的SQL语句中,参数都是⽤'?'占位符来表⽰的。 NamedParameterJdbcTemplate类内部封装了⼀个普通的JdbcTemplate,并作为其代理来完成⼤部分⼯作。下⾯的内容主要针对NamedParameterJdbcTemplate与JdbcTemplate的不同之处来加以说明,即如何在SQL语句中使⽤命名参数。通过下⾯的例⼦我们可以更好地了解NamedParameterJdbcTemplate的使⽤模式// some JDBC-backed public int countOfActorsByFirstName(String firstName) { String sql = "select count(0) from T_ACTOR where first_name = :first_name"; NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(aSource()); SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName); return orInt(sql, namedParameters);}如果你喜欢的话,也可以使⽤基于Map风格的名值对将命名参数传递给NamedParameterJdbcTemplate(NamedParameterJdbcTemplate实现了NamedParameterJdbcOperations接⼝,剩下的⼯作将由调⽤该接⼝的相应⽅法来完成):

// some JDBC-backed public int countOfActorsByFirstName(String firstName) { String sql = "select count(0) from T_ACTOR where first_name = :first_name"; NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(aSource()); Map namedParameters = new HashMap(); ("first_name", firstName); return orInt(sql, namedParameters);}SqlParameterSource接⼝的另⼀个实现--BeanPropertySqlParameterSource为我们提供了更有趣的功能。该类包装⼀个类似JavaBean的对象,所需要的命名参数值将由包装对象提供,下⾯我们使⽤⼀个例⼦来更清楚地说明它的⽤法。// some public class Actor { private Long id; private String firstName; private String lastName;

public String getFirstName() { return ame; }

public String getLastName() { return me; }

public Long getId() { return ; }

// }// some JDBC-backed public int countOfActors(Actor exampleActor) { // notice how the named parameters match the properties of the above 'Actor' class String sql = "select count(0) from T_ACTOR where first_name = :firstName and last_name = :lastName"; NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(aSource()); SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor); return orInt(sql, namedParameters);}注意:NamedParameterJdbcTemplate类是线程安全的,该类的最佳使⽤⽅式不是每次操作的时候实例化⼀个新的NamedParameterJdbcTemplate,⽽是针对每个DataSource只配置⼀个NamedParameterJdbcTemplate实例(⽐如在Spring IoC容器中使⽤Spring IoC来进⾏配置),然后在那些使⽤该类的DAO中共享该实例。1.3、SimpleJdbcTemplate类SimpleJdbcTemplate 类利⽤Java 5的语法特性带来的好处可以通过⼀个例⼦来说明。在下⾯的代码⽚断中我们⾸先使⽤标准的JdbcTemplate进⾏数据访问,接下来使⽤SimpleJdbcTemplate做同样的事情。// public Actor findActor(long id) { String sql = "select id, first_name, last_name from T_ACTOR where id = ?"; RowMapper mapper = new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Actor actor = new Actor(); (g(f(g("id")))); stName(ing("first_name")); tName(ing("last_name")); return actor; } }; // normally this would be dependency injected JdbcTemplate jdbcTemplate = new JdbcTemplate(aSource()); // notice the cast, and the wrapping up of the 'id' argument // in an array, and the boxing of the 'id' argument as a reference type return (Actor) orObject(sql, mapper, new Object[] {f(id)});}下⾯是同⼀⽅法的另⼀种实现,惟⼀不同之处是我们使⽤了SimpleJdbcTemplate,这样代码显得更加清晰。 // public Actor findActor(long id) { String sql = "select id, first_name, last_name from T_ACTOR where id = ?";

ParameterizedRowMapper mapper = new ParameterizedRowMapper() {

// notice the return type with respect to Java 5 covariant return types public Actor mapRow(ResultSet rs, int rowNum) throws SQLException { Actor actor = new Actor(); (g("id")); stName(ing("first_name")); tName(ing("last_name")); return actor; } };

// again, normally this would be dependency injected SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(aSource()); return orObject(sql, mapper, id);}1.4、DataSource接⼝为了从数据库中取得数据,我们⾸先需要获取⼀个数据库连接。 Spring通过DataSource对象来完成这个⼯作。DataSource是JDBC规范的⼀部分, 它被视为⼀个通⽤的数据库连接⼯⼚。通过使⽤DataSource, Container或Framework可以将连接池以及事务管理的细节从应⽤代码中分离出来。 作为⼀个开发⼈员,在开发和测试产品的过程中,你可能需要知道连接数据库的细节。 但在产品实施时,你不需要知道这些细节。通常数据库管理员会帮你设置好数据源。在使⽤Spring JDBC时,你既可以通过JNDI获得数据源,也可以⾃⾏配置数据源(使⽤Spring提供的DataSource实现类)。使⽤后者可以更⽅便的脱离Web容器来进⾏单元测试。 这⾥我们将使⽤DriverManagerDataSource,不过DataSource有多种实现, 后⾯我们会讲到。使⽤DriverManagerDataSource和你以前获取⼀个JDBC连接 的做法没什么两样。你⾸先必须指定JDBC驱动程序的全限定名,这样DriverManager 才能加载JDBC驱动类,接着你必须提供⼀个url(因JDBC驱动⽽异,为了保证设置正确请参考相关JDBC驱动的⽂档), 最后你必须提供⼀个⽤户连接数据库的⽤户名和密码。下⾯我们将通过⼀个例⼦来说明如何配置⼀个 DriverManagerDataSource:DriverManagerDataSource dataSource = new DriverManagerDataSource();verClassName("iver");("jdbc:hsqldb:hsql://localhost:");rname("sa");sword("");1.5、SQLExceptionTranslator接⼝SQLErrorCodeSQLExceptionTranslator是SQLExceptionTranslator的默认实现。 该实现使⽤指定数据库⼚商的errorcode,⽐采⽤SQLState更精确。 转换过程基于⼀个JavaBean(类型为SQLErrorCodes)中的error code。 这个JavaBean由SQLErrorCodesFactory⼯⼚类创建,其中的内容来⾃于 ""配置⽂件。该⽂件中的数据库⼚商代码基于Database MetaData信息中的 DatabaseProductName,从⽽配合当前数据库的使⽤。SQLErrorCodeSQLExceptionTranslator使⽤以下的匹配规则:● ⾸先检查是否存在完成定制转换的⼦类实现。通常SQLErrorCodeSQLExceptionTranslator 这个类可以作为⼀个具体类使⽤,不需要进⾏定制,那么这个规则将不适⽤。● 接着将SQLException的error code与错误代码集中的error code进⾏匹配。 默认情况下错误代码集将从SQLErrorCodesFactory取得。 错误代码集来⾃classpath下的⽂件, 它们将与数据库metadata信息中的database name进⾏映射。● 如果仍然⽆法匹配,最后将调⽤fallbackTranslator属性的translate⽅法,SQLStateSQLExceptionTranslator类实例是默认的fallbackTranslator。SQLErrorCodeSQLExceptionTranslator可以采⽤下⾯的⽅式进⾏扩展:public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) { if (orCode() == -12345) { return new DeadlockLoserDataAccessException(task, sqlex); } return null; }}在上⾯的这个例⼦中,error code为'-12345'的SQLException 将采⽤该转换器进⾏转换,⽽其他的error code将由默认的转换器进⾏转换。 为了使⽤该转换器,必须将其作为参数传递给JdbcTemplate类 的setExceptionTranslator⽅法,并在需要使⽤这个转换器器的数据 存取操作中使⽤该JdbcTemplate。 下⾯的例⼦演⽰了如何使⽤该定制转换器:

// create a JdbcTemplate and set data sourceJdbcTemplate jt = new JdbcTemplate();aSource(dataSource);// create a custom translator and set the DataSource for the default translation lookupMySQLErrorCodesTransalator tr = new MySQLErrorCodesTransalator();aSource(dataSource);eptionTranslator(tr);// use the JdbcTemplate for this SqlUpdateSqlUpdate su = new SqlUpdate();cTemplate(jt);("update orders set shipping_charge = shipping_charge * 1.05");e();();在上⾯的定制转换器中,我们给它注⼊了⼀个数据源,因为我们仍然需要 使⽤默认的转换器从中获取错误代码集。

1.6、执⾏SQL语句我们仅需要⾮常少的代码就可以达到执⾏SQL语句的⽬的,⼀旦获得⼀个 DataSource和⼀个JdbcTemplate, 我们就可以使⽤JdbcTemplate提供的丰富功能实现我们的操作。 下⾯的例⼦使⽤了极少的代码完成创建⼀张表的⼯作。import urce;import mplate;public class ExecuteAStatement {private JdbcTemplate jt;private DataSource dataSource; public void doExecute() { jt = new JdbcTemplate(dataSource); e("create table mytable (id integer, name varchar(100))"); } public void setDataSource(DataSource dataSource) { urce = dataSource; }}1.7、执⾏查询除了execute⽅法之外,JdbcTemplate还提供了⼤量的查询⽅法。 在这些查询⽅法中,有很⼤⼀部分是⽤来查询单值的。⽐如返回⼀个汇总(count)结果 或者从返回⾏结果中取得指定列的值。这时我们可以使⽤queryForInt(..)、queryForLong(..)或者queryForObject(..)⽅法。 queryForObject⽅法⽤来将返回的JDBC类型对象转换成指定的Java对象,如果类型转换失败将抛出 InvalidDataAccessApiUsageException异常。 下⾯的例⼦演⽰了两个查询的⽤法,⼀个返回int值,另⼀个返回 String。import urce;import mplate;public class RunAQuery { private JdbcTemplate jt; private DataSource dataSource; public int getCount() { jt = new JdbcTemplate(dataSource); int count = orInt("select count(*) from mytable"); return count; } public String getName() { jt = new JdbcTemplate(dataSource); String name = (String) orObject("select name from mytable", ); return name; } public void setDataSource(DataSource dataSource) { urce = dataSource; }}除了返回单值的查询⽅法,JdbcTemplate还提供了⼀组返回List结果 的⽅法。List中的每⼀项对应查询返回结果中的⼀⾏。其中最简单的是queryForList⽅法, 该⽅法将返回⼀个List,该List中的每⼀条 记录是⼀个Map对象,对应应数据库中某⼀⾏;⽽该Map 中的每⼀项对应该数据库⾏中的某⼀列值。下⾯的代码⽚断接着上⾯的例⼦演⽰了如何⽤该⽅法返回表中 所有记录:public List getList() { jt = new JdbcTemplate(dataSource); List rows = orList("select * from mytable"); return rows;}返回的结果集类似下⾯这种形式:[{name=Bob, id=1}, {name=Mary, id=2}]1.8、更新数据库JdbcTemplate还提供了⼀些更新数据库的⽅法。 在下⾯的例⼦中,我们根据给定的主键值对指定的列进⾏更新。 例⼦中的SQL语句中使⽤了“?”占位符来接受参数(这种做法在更新和查询SQL语句中很常见)。 传递的参数值位于⼀个对象数组中(基本类型需要被包装成其对应的对象类型)。import urce;import mplate;public class ExecuteAnUpdate { private JdbcTemplate jt; private DataSource dataSource;

public void setName(int id, String name) { jt = new JdbcTemplate(dataSource); ("update mytable set name = ? where id = ?", new Object[] {name, new Integer(id)}); } public void setDataSource(DataSource dataSource) { urce = dataSource; }}2.控制数据库连接2.1、DataSourceUtils类DataSourceUtils作为⼀个帮助类提供易⽤且强⼤的数据库访问能⼒, 我们可以使⽤该类提供的静态⽅法从JNDI获取数据库连接以及在必要的时候关闭之。 它提供⽀持线程绑定的数据库连接(⽐如使⽤DataSourceTransactionManager 的时候,将把数据库连接绑定到当前的线程上)。注:getDataSourceFromJndi(..)⽅法主要⽤于那些没有使⽤bean factory 或者application context的场合。如果使⽤application context,那么最好是在 JndiObjectFactoryBean中配置bean或者直接使⽤ JdbcTemplate实例。JndiObjectFactoryBean 能够通过JNDI获取DataSource并将 DataSource作为引⽤参数传递给其他bean。 这样,在不同的DataSource之间切换只需要修改配置⽂件即可, 甚⾄我们可以⽤⼀个⾮JNDI的DataSource来替换 FactoryBean定义!2.2、SmartDataSource接⼝SmartDataSource是DataSource 接⼝的⼀个扩展,⽤来提供数据库连接。使⽤该接⼝的类在指定的操作之后可以检查是否需要关闭连接。 该接⼝在某些情况下⾮常有⽤,⽐如有些情况需要重⽤数据库连接。2.3、AbstractDataSource类AbstractDataSource是⼀个实现了DataSource 接⼝的abstract基类。它实现了DataSource接⼝的 ⼀些⽆关痛痒的⽅法,如果你需要实现⾃⼰的DataSource,那么继承 该类是个好主意。2.4、SingleConnectionDataSource类SingleConnectionDataSource是SmartDataSource接⼝ 的⼀个实现,其内部包装了⼀个单连接。该连接在使⽤之后将不会关闭,很显然它不能在多线程 的环境下使⽤。当客户端代码调⽤close⽅法的时候,如果它总是假设数据库连接来⾃连接池(就像使⽤持久化⼯具时⼀样), 你应该将suppressClose设置为true。 这样,通过该类获取的将是代理连接(禁⽌关闭)⽽不是原有的物理连接。 需要注意的是,我们不能把使⽤该类获取的数据库连接造型(cast)为Oracle Connection之类的本地数据库连接。SingleConnectionDataSource主要在测试的时候使⽤。 它使得测试代码很容易脱离应⽤服务器⽽在⼀个简单的JNDI环境下运⾏。 与DriverManagerDataSource不同的是,它始终只会使⽤同⼀个数据库连接, 从⽽避免每次建⽴物理连接的开销。

2.5、DriverManagerDataSource类DriverManagerDataSource类实现了 SmartDataSource接⼝。在中可以使⽤ beanproperties来设置JDBC Driver属性,该类每次返回的都是⼀个新的连接。该类主要在测试以及脱离J2EE容器的独⽴环境中使⽤。它既可以⽤来在application context中作为⼀个 DataSourcebean,也可以在简单的JNDI环境下使⽤。 由于()仅仅只是简单的关闭数据库连接,因此任何能够获取DataSource的持久化代码都能很好的⼯作。不过使⽤JavaBean风格的连接池 (⽐如commons-dbcp)也并⾮难事。即使是在测试环境下,使⽤连接池也是⼀种⽐使⽤ DriverManagerDataSource更好的做法。2.6、TransactionAwareDataSourceProxy类TransactionAwareDataSourceProxy作为⽬标DataSource的⼀个代理, 在对⽬标DataSource包装的同时,还增加了Spring的事务管理能⼒, 在这⼀点上,这个类的功能⾮常像J2EE服务器所提供的事务化的JNDI DataSource。注意:该类⼏乎很少被⽤到,除⾮现有代码在被调⽤的时候需要⼀个标准的 JDBC DataSource接⼝实现作为参数。 这种情况下,这个类可以使现有代码参与Spring的事务管理。通常最好的做法是使⽤更⾼层的抽象 来对数据源进⾏管理,⽐如JdbcTemplate和DataSourceUtils等等。2.7、DataSourceTransactionManager类DataSourceTransactionManager类是 PlatformTransactionManager接⼝的⼀个实现,⽤于处理单JDBC数据源。 它将从指定DataSource取得的JDBC连接绑定到当前线程,因此它也⽀持了每个数据源对应到⼀个线程。我们推荐在应⽤代码中使⽤nection(DataSource)来获取 JDBC连接,⽽不是使⽤J2EE标准的nection。因为前者将抛出 unchecked的异常,⽽不是checked的SQLException异常。Spring Framework中所有的类(⽐如 JdbcTemplate)都采⽤这种做法。如果不需要和这个DataSourceTransactionManager类⼀起使⽤,DataSourceUtils 提供的功能跟⼀般的数据库连接策略没有什么两样,因此它可以在任何场景下使⽤。DataSourceTransactionManager类⽀持定制隔离级别,以及对SQL语句查询超时的设定。 为了⽀持后者,应⽤代码必须使⽤JdbcTemplate或者在每次创建SQL语句时调⽤ ransactionTimeout⽅法。在使⽤单个数据源的情形下,你可以⽤DataSourceTransactionManager来替代JtaTransactionManager, 因为DataSourceTransactionManager不需要容器⽀持JTA。如果你使⽤nection(DataSource)来获取 JDBC连接,⼆者之间的切换只需要更改⼀些配置。最后需要注意的⼀点就是JtaTransactionManager不⽀持隔离级别的定制!3.⽤Java对象来表达JDBC操作包下的类允许⽤户以更加 ⾯向对象的⽅式去访问数据库。⽐如说,⽤户可以执⾏查询并返回⼀个list, 该list作为⼀个结果集将把从数据库中取出的列数据映射到业务对象的属性上。 ⽤户也可以执⾏存储过程,以及运⾏更新、删除以及插⼊SQL语句。注意:必须说明的⼀点就是,这仅仅只是⼀种观点⽽已, 如果你认为你可以从直接使⽤RDBMS操作类中获取⼀些额外的好处, 你不妨根据⾃⼰的需要和喜好进⾏不同的选择。3.1、SqlQuery类SqlQuery是⼀个可重⽤、线程安全的类,它封装了⼀个SQL查询。 其⼦类必须实现newResultReader()⽅法,该⽅法⽤来在遍历 ResultSet的时候能使⽤⼀个类来保存结果。 我们很少需要直接使⽤SqlQuery,因为其⼦类 MappingSqlQuery作为⼀个更加易⽤的实现能够将结果集中的⾏映射为Java对象。 SqlQuery还有另外两个扩展分别是MappingSqlQueryWithParameters和UpdatableSqlQuery。3.2、MappingSqlQuery类MappingSqlQuery是⼀个可重⽤的查询抽象类,其具体类必须实现 mapRow(ResultSet, int)抽象⽅法来将结果集中的每⼀⾏转换成Java对象。在SqlQuery的各种实现中, MappingSqlQuery是最常⽤也是最容易使⽤的⼀个。下⾯这个例⼦演⽰了⼀个定制查询,它将从客户表中取得的数据映射到⼀个 Customer类实例。private class CustomerMappingQuery extends MappingSqlQuery { public CustomerMappingQuery(DataSource ds) { super(ds, "SELECT id, name FROM customer WHERE id = ?"); eParameter(new SqlParameter("id", R)); compile(); }

public Object mapRow(ResultSet rs, int rowNumber) throws SQLException { Customer cust = new Customer(); ((Integer) ect("id")); e(ing("name")); return cust; }}在上⾯的例⼦中,我们为⽤户查询提供了⼀个构造函数并为构造函数传递了⼀个 DataSource参数。在构造函数⾥⾯我们把DataSource和⼀个⽤来返回查询结果的SQL语句作为参数 调⽤⽗类的构造函数。SQL语句将被⽤于⽣成⼀个PreparedStatement对象, 因此它可以包含占位符来传递参数。⽽每⼀个SQL语句的参数必须通过调⽤declareParameter⽅法来进⾏声明,该⽅法需要⼀个 SqlParameter(封装了⼀个字段名字和⼀个 中定义的JDBC类型)对象作为参数。 所有参数定义完之后,我们调⽤compile()⽅法来对SQL语句进⾏预编译。下⾯让我们看看该定制查询初始化并执⾏的代码:

public Customer getCustomer(Integer id) { CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource); Object[] parms = new Object[1]; parms[0] = id; List customers = e(parms);

if (() > 0) { return (Customer) (0); }else { return null; }}在上⾯的例⼦中,getCustomer⽅法通过传递惟⼀参数id来返回⼀个客户对象。 该⽅法内部在创建CustomerMappingQuery实例之后, 我们创建了⼀个对象数组⽤来包含要传递的查询参数。这⾥我们只有唯⼀的⼀个Integer参数。执⾏CustomerMappingQuery的 execute⽅法之后,我们得到了⼀个List,该List中包含⼀个 Customer对象,如果有对象满⾜查询条件的话。

3.3、SqlUpdate类SqlUpdate类封装了⼀个可重复使⽤的SQL更新操作。 跟所有RdbmsOperation类⼀样,SqlUpdate可以在SQL中定义参数。该类提供了⼀系列update()⽅法,就像SqlQuery提供的⼀系列execute()⽅法⼀样。SqlUpdate是⼀个具体的类。通过在SQL语句中定义参数,这个类可以⽀持 不同的更新⽅法,我们⼀般不需要通过继承来实现定制。import Types;import Parameter;import Procedure;import urce;import p;import ;import ;import urce;import ameter;import ate;public class UpdateCreditRating extends SqlUpdate { public UpdateCreditRating(DataSource ds) { setDataSource(ds); setSql("update customer set credit_rating = ? where id = ?"); declareParameter(new SqlParameter(C)); declareParameter(new SqlParameter(C)); compile(); } /** * @param id for the Customer to be updated * @param rating the new value for credit rating * @return number of rows updated */ public int run(int id, int rating) { Object[] params = new Object[] { new Integer(rating), new Integer(id)}; return update(params); }}3.4、StoredProcedure类StoredProcedure类是⼀个抽象基类,它是对RDBMS存储过程的⼀种抽象。 该类提供了多种execute(..)⽅法,不过这些⽅法的访问类型都是protected的。从⽗类继承的sql属性⽤来指定RDBMS存储过程的名字。 尽管该类提供了许多必须在JDBC3.0下使⽤的功能,但是我们更关注的是JDBC 3.0中引⼊的命名参数特性。下⾯的程序演⽰了如何调⽤Oracle中的sysdate()函数。 这⾥我们创建了⼀个继承StoredProcedure的⼦类,虽然它没有输⼊参数, 但是我必须通过使⽤SqlOutParameter来声明⼀个⽇期类型的输出参数。 execute()⽅法将返回⼀个map,map中的每个entry是⼀个⽤参数名作key, 以输出参数为value的名值对。import ;import p;import or;import ;import urce;import Parameter;import urce.*;import Procedure;public class TestStoredProcedure { public static void main(String[] args) { TestStoredProcedure t = new TestStoredProcedure(); (); n("Done!"); } void test() { DriverManagerDataSource ds = new DriverManagerDataSource(); verClassName("Driver"); ("jdbc:oracle:thin:@localhost:1521:mydb"); rname("scott"); sword("tiger"); MyStoredProcedure sproc = new MyStoredProcedure(ds); Map results = e(); printMap(results); } private class MyStoredProcedure extends StoredProcedure { private static final String SQL = "sysdate"; public MyStoredProcedure(DataSource ds) { setDataSource(ds); setFunction(true); setSql(SQL); declareParameter(new SqlOutParameter("date", )); compile(); } public Map execute() { // the 'sysdate' sproc has no input parameters, so an empty Map return execute(new HashMap()); } } private static void printMap(Map results) { for (Iterator it = et().iterator(); t(); ) { n(()); } }}下⾯是StoredProcedure的另⼀个例⼦,它使⽤了两个Oracle游标类型的输出参数。 import Types;import Parameter;import Procedure;import urce;import p;import ;public class TitlesAndGenresStoredProcedure extends StoredProcedure { private static final String SPROC_NAME = "AllTitlesAndGenres"; public TitlesAndGenresStoredProcedure(DataSource dataSource) { super(dataSource, SPROC_NAME); declareParameter(new SqlOutParameter("titles", , new TitleMapper())); declareParameter(new SqlOutParameter("genres", , new GenreMapper())); compile(); } public Map execute() { // again, this sproc has no input parameters, so an empty Map return e(new HashMap()); }}值得注意的是TitlesAndGenresStoredProcedure构造函数中 declareParameter(..)的SqlOutParameter参数, 该参数使⽤了RowMapper接⼝的实现。 这是⼀种⾮常⽅便⽽强⼤的重⽤⽅式。 下⾯我们来看⼀下RowMapper的两个具体实现。⾸先是TitleMapper类,它简单的把ResultSet中的每⼀⾏映射为⼀个Title Domain Object。

import ;import per;import Set;import eption;public final class TitleMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Title title = new Title(); (g("id")); e(ing("name")); return title; }}另⼀个是GenreMapper类,也是⾮常简单的将ResultSet中的每⼀⾏映射为⼀个Genre Domain Object。

import per;import Set;import eption;import ;public final class GenreMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { return new Genre(ing("name")); }}如果你需要给存储过程传输⼊参数(这些输⼊参数是在RDBMS存储过程中定义好了的), 则需要提供⼀个指定类型的execute(..)⽅法, 该⽅法将调⽤基类的protected execute(Map parameters)⽅法。 例如:

public class TitlesAfterDateStoredProcedure extends StoredProcedure { private static final String SPROC_NAME = "TitlesAfterDate"; private static final String CUTOFF_DATE_PARAM = "cutoffDate";

public TitlesAfterDateStoredProcedure(DataSource dataSource) { super(dataSource, SPROC_NAME); declaraParameter(new SqlParameter(CUTOFF_DATE_PARAM, ); declareParameter(new SqlOutParameter("titles", , new TitleMapper())); compile(); } public Map execute(Date cutoffDate) { Map inputs = new HashMap(); (CUTOFF_DATE_PARAM, cutoffDate); return e(inputs); }}3.5、SqlFunction类SqlFunction RDBMS操作类封装了⼀个SQL“函数”包装器(wrapper), 该包装器适⽤于查询并返回⼀个单⾏结果集。默认返回的是⼀个int值, 不过我们可以采⽤类似JdbcTemplate中的queryForXxx 做法⾃⼰实现来返回其它类型。SqlFunction优势在于我们不必创建 JdbcTemplate,这些它都在内部替我们做了。该类的主要⽤途是调⽤SQL函数来返回⼀个单值的结果集,⽐如类似“select user()”、 “select sysdate fromdual”的查询。如果需要调⽤更复杂的存储函数, 可以使⽤StoredProcedure或SqlCall。SqlFunction是⼀个具体类,通常我们不需要它的⼦类。 其⽤法是创建该类的实例,然后声明SQL语句以及参数就可以调⽤相关的run⽅法去多次执⾏函数。 下⾯的例⼦⽤来返回指定表的记录⾏数:public int countRows() { SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable"); e(); return ();}