1.Spring 中使用 JdbcTemplate 封装对 Jdbc 的支持,使用 Spring JdbcTeamplate 的例子如下:
(1). 假设如下 SQL 表中有数据 username=test1,passwd=test1,address=test1 :
CREATE TABLE `login` ( `username` varchar(10) default NULL, `passwd` varchar(10) default NULL, `address` varchar(10) default NULL ) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
(2). 在 Spring 配置文件中添加关于数据源和 JdbcTeamplate 的配置如下:
com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/javaee root 1234
(3).Java 持久化对象如下:
package SpringJDBCSupport.ReadData; import com.mysql.jdbc.Driver; public class Person { private String name; private String password; private String address; public Person(){ } public Person(String name,String password,String address){ this.name=name; this.password=password; this.address=address; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String toString(){ return this.getName()+"-"+this.getPassword()+"-"+this.getAddress(); } }
(4). 使用 JdbcTeamplate 的 DAO 如下:
package SpringJDBCSupport.ReadData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.List; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowCallbackHandler; public class PersonDAO { private JdbcTemplate jdbcTemplate; public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public int insertPersonUseUpdate(Person person){ String sql="insert into login values(?,?,?)"; Object[] params=new Object[]{ person.getName(), person.getPassword(), person.getAddress() }; return this.getJdbcTemplate().update(sql,params); } public int insertPersonUseExecute(Person person){ String sql="insert into login values(?,?,?)"; Object[] params=new Object[]{ person.getName(), person.getPassword(), person.getAddress() }; int[] types=new int[]{ Types.VARCHAR, Types.VARCHAR, Types.VARCHAR }; return this.getJdbcTemplate().update(sql,params,types); } public int[] updatePersonUseBatchUpdate( final List persons){ String sql="insert into login values(?,?,?)"; BatchPreparedStatementSetter setter=null; //使用匿名内部类,实现BatchPreparedStatementSetter接口,实现批量更新 setter=new BatchPreparedStatementSetter(){ public int getBatchSize(){ return persons.size(); } public void setValues(PreparedStatement ps,int index) throws SQLException{ Person person=(Person)persons.get(index); ps.setString(1,person.getName()); ps.setString(2,person.getPassword()); ps.setString(3,person.getAddress()); } }; return this.getJdbcTemplate().batchUpdate(sql,setter); } public Person getPersonByRowCallbackHandler(String username){ String sql="select * from login where username=?"; final Person person=new Person(); final Object params[]=new Object[]{username}; //使用匿名内部类,实现RowCallbackHandler接口,即实现查询结果集的RowMapping this.getJdbcTemplate().query(sql,params,new RowCallbackHandler(){ public void processRow(ResultSet rs)throws SQLException{ person.setName(rs.getString("username")); person.setPassword(rs.getString("passwd")); person.setAddress(rs.getString("address")); } }); return person; } }
2.Spring JdbcTemplate 的工作流程:
通过 1 中的小例子,我们可以总结出 SpringJdbcTemplate 的工作流程如下:
(1). 配置数据源:
Spring 中,将管理数据库连接的数据源当作普通 Java Bean 一样在 Spring IoC 容器中管理,当应用使用数据源时 Spring IoC 容器负责初始化数据源。
(2). 将数据源注入 JdbcTemplate :
JdbcTemplate 中 dataSource 属性用于注入配置的数据源, Spring IoC 容器通过依赖注入将配置的数据源注入到 Spring 对 Jdbc 操作的封装类 JdbcTemplate 中。
(3). 应用中使用 JdbcTemplate :
注入了数据源的 JdbcTemplate 就可以在应用中使用了,应用中对数据源的增删改查等操作都可以使用 JdbcTemplate 对外提供的方法操作数据库。
3.JdbcTemplate 处理 Statement 的相关方法实现:
JdbcTemplate 的 execute 方法是 JdbcTemplate 的核心方法,是 JdbcTemplate 调用 jdbc 进行查询,添加,删除和更新操作的基础方法,在 execute 方法中,主要实现对数据库的基本操作,如:获取数据库连接;根据应用对数据库的需要创建数据库的 Statement ;对数据库操作进行回调;处理数据库异常;关闭数据库连接等等。 JdbcTemplate 中有真的 Jdbc 中 Statement 、 PreparedStatement 和 CallableStatement 处理的 execute 方法 ,首先我们分析处理 Statement 的相关方法:
(1). 处理 Statement 的 execute 方法:
//execute方法执行的是输入的sql语句,且没有返回值的 public void execute(final String sql) throws DataAccessException { if (logger.isDebugEnabled()) { logger.debug("Executing SQL statement [" + sql + "]"); } //内部类,实现类StatementCallback和SqlProvider接口,执行sql语句的回调类 class ExecuteStatementCallback implements StatementCallback, SqlProvider { //JdbcTemplate中真正执行输入的sql语句的地方 public Object doInStatement(Statement stmt) throws SQLException { stmt.execute(sql); return null; } //获取输入的sql语句 public String getSql() { return sql; } } //调用通用的处理静态sql语句的execute方法 execute(new ExecuteStatementCallback()); } //通用的处理静态sql语句 public T execute(StatementCallback action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); //根据配置的数据源获取数据库连接 Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { Connection conToUse = con; //如果JdbcTemplate指定了本地连接,则将获取到的数据库连接转换为本地连接 if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } //创建Statement stmt = conToUse.createStatement(); applyStatementSettings(stmt); Statement stmtToUse = stmt; //如果JdbcTemplate指定了本地连接,则将Statement转换为本地Statement if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); } //调用ExecuteStatementCallback类的doInStatement回调方法,执行sql语句 T result = action.doInStatement(stmtToUse); handleWarnings(stmt); //返回执行结果 return result; } catch (SQLException ex) { //产生异常,则关闭Statement JdbcUtils.closeStatement(stmt); stmt = null; //释放数据库连接 DataSourceUtils.releaseConnection(con, getDataSource()); con = null; //将数据库异常封装为Spring异常向调用者抛出 throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex); } finally { //关闭Statement,释放数据库连接 JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } }
Execute 方法是 JdbcTemplate 执行 jdbc 操作的核心,其他的方法都是通过调用 execute 方法来操作数据库。
(2). Statement 的查询方法:
JdbcTemplate 处理 Statement 的查询方法有很多,但是其中最基本的方法源码如下:
//静态sql的查询方法,第二个参数ResultSetExtractor是查询结果集转换器,用于处理查//询得到的结果集 public T query(final String sql, final ResultSetExtractor rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { logger.debug("Executing SQL query [" + sql + "]"); } //实现了StatementCallback和SqlProvider接口的内部类,用于execute方法回调 class QueryStatementCallback implements StatementCallback, SqlProvider { //execute方法执行查询操作时回调方法,真正执行jdbc操作的方法 public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; try { //调用Statement的查询方法 rs = stmt.executeQuery(sql); ResultSet rsToUse = rs; //如果JdbcTemplate指定了本地jdbc提取器,则将查询得到的结果 //集提取为本地结果集 if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } //使用结果集提取器对查询得到的结果集进行处理 return rse.extractData(rsToUse); } finally { //关闭结果集 JdbcUtils.closeResultSet(rs); } } //获取sql public String getSql() { return sql; } } //调用处理Statement的execute方法 return execute(new QueryStatementCallback()); }
(3).Statement 的更新方法:
//静态sql的更新方法 public int update(final String sql) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); if (logger.isDebugEnabled()) { logger.debug("Executing SQL update [" + sql + "]"); } //实现了StatementCallback和SqlProvider接口的内部类,Statement //的execute方法回调 class UpdateStatementCallback implements StatementCallback, SqlProvider { //Statement的execute方法更新操作时回调方法,真正执行jdbc操作的方法 public Integer doInStatement(Statement stmt) throws SQLException { //Statement执行jdbc的更新操作,返回影响行数 int rows = stmt.executeUpdate(sql); if (logger.isDebugEnabled()) { logger.debug("SQL update affected " + rows + " rows"); } return rows; } public String getSql() { return sql; } } //调用Statement的execute方法 return execute(new UpdateStatementCallback()); }
通过对 Statement 相关处理的方法源码分析,我们可以看出 execute 方法是核心方法,在 execute 方法中,主要获取数据库连接和创建 Statement ,同时当执行完 jdbc 操作之后释放连接和资源等数据库操作的通用流程,所有的查询,更新等具体操作均是通过向 execute 方法传递合适的回调参数来使用 execute 方法中的数据库通用流程和资源,真正执行 jdbc 操作的方法由具体的回调内部类来实现。
4. JdbcTemplate 处理 PreparedStatement 的相关方法实现:
(1). 处理 PreparedStatement 的 execute 方法:
//PreparedStatement处理sql语句的execute方法 public T execute(String sql, PreparedStatementCallback action) throws DataAccessException { //将sql语句封装成为SimplePreparedStatementCreator,调用处理//PreparedStatement的方法 return execute(new SimplePreparedStatementCreator(sql), action); } //处理PreparedStatement public T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null"); Assert.notNull(action, "Callback object must not be null"); if (logger.isDebugEnabled()) { String sql = getSql(psc); logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); } //获取数据库连接 Connection con = DataSourceUtils.getConnection(getDataSource()); PreparedStatement ps = null; try { Connection conToUse = con; //如果JdbcTemplate指定了jdbc本地提取器,则将获取到的数据库连接转换 //为本地数据库连接 if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } //根据数据库连接和sql语句创建PreparedStatement ps = psc.createPreparedStatement(conToUse); applyStatementSettings(ps); PreparedStatement psToUse = ps; //如果JdbcTemplate指定了jdbc本地提取器,则将PreparedStatement转换 //为本地PreparedStatement if (this.nativeJdbcExtractor != null) { psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps); } //调用回调处理器,执行相应操作 T result = action.doInPreparedStatement(psToUse); handleWarnings(ps); return result; } catch (SQLException ex) { //关闭资源,释放连接 if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } String sql = getSql(psc); psc = null; JdbcUtils.closeStatement(ps); ps = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; //将jdbc相关异常封装转换为Spring异常向调用者抛出 throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex); } finally { //清除PreparedStatement的参数 if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } //关闭PreparedStatement JdbcUtils.closeStatement(ps); //释放数据库连接 DataSourceUtils.releaseConnection(con, getDataSource()); } }
(2). PreparedStatement 的查询方法:
和 Statement 类似, JdbcTemplate 中 PreparedStatement 的查询方法也非常多,我们以最基本的查询方法源码为例分析其处理流程:
//PreparedStatement查询方法,第一个参数PreparedStatementCreator为封装sql语句的//类,第二个参数PreparedStatementSetter为向PreparedStatement设置参数的类,第三//个参数ResultSetExtractor为处理结果集的类 public T query( PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse) throws DataAccessException { Assert.notNull(rse, "ResultSetExtractor must not be null"); logger.debug("Executing prepared SQL query"); //调用PreparedStatement的execute方法,第二个参数为一个实现了//PreparedStatementCallback接口的匿名内部类,被execute回调 return execute(psc, new PreparedStatementCallback() { //执行jdbc操作的方法 public T doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { //如果PreparedStatement参数不为null,则为PreparedStatement设置参数值 if (pss != null) { pss.setValues(ps); } //执行jdbc查询操作 rs = ps.executeQuery(); ResultSet rsToUse = rs; //如果JdbcTemplate指定了本地jdbc提取器,则将查询得到的结果 //集转换为本地jdbc结果集 if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } //使用配置的结果集处理器提取结果集数据 return rse.extractData(rsToUse); } finally { //关闭结果集 JdbcUtils.closeResultSet(rs); //清除PreparedStatement参数 if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }); }
(3). PreparedStatement 的更新方法:
// PreparedStatement更新方法,第二个参数PreparedStatementSetter为向//PreparedStatement设置参数的类 protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss) throws DataAccessException { logger.debug("Executing prepared SQL update"); //调用PreparedStatement的execute方法,第二个参数为实现了//PreparedStatementCallback接口的匿名内部类,由PreparedStatement的 //execute方法回调 return execute(psc, new PreparedStatementCallback() { //真正执行jdbc操作的方法 public Integer doInPreparedStatement(PreparedStatement ps) throws SQLException { try { //为PreparedStatement设置参数值 if (pss != null) { pss.setValues(ps); } //PreparedStatement调用jdbc的更新操作,返回受影响的行数 int rows = ps.executeUpdate(); if (logger.isDebugEnabled()) { logger.debug("SQL update affected " + rows + " rows"); } return rows; } finally { //清除PreparedStatement参数 if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }); }
通过对 PreparedStatement 相关处理方法的源码分析,我们可以看到 PreparedStatement 和 Statement 的处理流程基本是相同的,不同之处在于 PreparedStatement 需要处理设置参数值的操作。
5.JdbcTemplate 处理 CallableStatement 的相关方法实现:
(1). 处理 CallableStatement 的 execute 方法:
//CallableStatement处理给定字符串的 public T execute(String callString, CallableStatementCallback action) throws DataAccessException { //将字符串封装为SimpleCallableStatementCreator类,调用execute方法处//理CallableStatement return execute(new SimpleCallableStatementCreator(callString), action); } //CallableStatement的execute方法,第一个参数CallableStatementCreator是封装调用字//符串的类,封装调用数据库存储过程的语句 public T execute(CallableStatementCreator csc, CallableStatementCallback action) throws DataAccessException { Assert.notNull(csc, "CallableStatementCreator must not be null"); Assert.notNull(action, "Callback object must not be null"); if (logger.isDebugEnabled()) { String sql = getSql(csc); logger.debug("Calling stored procedure" + (sql != null ? " [" + sql + "]" : "")); } //根据数据源获取数据库连接 Connection con = DataSourceUtils.getConnection(getDataSource()); CallableStatement cs = null; try { Connection conToUse = con; //如果JdbcTemplate指定了本地jdbc提取器,则将获取到的数据库连接转换为 //本地数据库连接 if (this.nativeJdbcExtractor != null) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } //根据数据库连接创建CallableStatement cs = csc.createCallableStatement(conToUse); applyStatementSettings(cs); CallableStatement csToUse = cs; //如果JdbcTemplate指定了本地jdbc提取器,则将CallableStatement转换为 //本地CallableStatement if (this.nativeJdbcExtractor != null) { csToUse = this.nativeJdbcExtractor.getNativeCallableStatement(cs); } //调用相应回调对象的方法,执行jdbc操作 T result = action.doInCallableStatement(csToUse); handleWarnings(cs); return result; } catch (SQLException ex) { //清除CallableStatement参数 if (csc instanceof ParameterDisposer) { ((ParameterDisposer) csc).cleanupParameters(); } String sql = getSql(csc); csc = null; //关闭CallableStatement JdbcUtils.closeStatement(cs); cs = null; //释放数据库连接 DataSourceUtils.releaseConnection(con, getDataSource()); con = null; //将jdbc异常封装为Spring异常 throw getExceptionTranslator().translate("CallableStatementCallback", sql, ex); } finally { //清除CallableStatement参数 if (csc instanceof ParameterDisposer) { ((ParameterDisposer) csc).cleanupParameters(); } //关闭CallableStatement JdbcUtils.closeStatement(cs); //释放数据库连接 DataSourceUtils.releaseConnection(con, getDataSource()); } }
(2).CallableStatement 的 call 方法:
//CallableStatement调用数据库的存储过程 public Map call(CallableStatementCreator csc, List declaredParameters) throws DataAccessException { final List updateCountParameters = new ArrayList(); final List resultSetParameters = new ArrayList(); final List callParameters = new ArrayList(); //遍历声明的参数 for (SqlParameter parameter : declaredParameters) { //如果参数是结果参数 if (parameter.isResultsParameter()) { //如果参数是返回结果集类型,则将参数添加到结果集参数集合中 if (parameter instanceof SqlReturnResultSet) { resultSetParameters.add(parameter); } //如果参数不是返回结果集类型,则将参数添加到更新数目参数集合中 else { updateCountParameters.add(parameter); } } //如果参数不是结果参数,则将参数添加到调用参数集合中 else { callParameters.add(parameter); } } //调用CallableStatement的execute方法,第二个参数是实现了//CallableStatementCallback接口的匿名内部类,用于回调 return execute(csc, new CallableStatementCallback>() { //真正调用jdbc操作的方法 public Map doInCallableStatement(CallableStatement cs) throws SQLException { //CallableStatement执行jdbc调用,如果是返回结果为结果集则为//ture,如果执行返回结果不是结果集则返回false boolean retVal = cs.execute(); //获取CallableStatement执行后数据库中被更新的记录数 int updateCount = cs.getUpdateCount(); if (logger.isDebugEnabled()) { logger.debug("CallableStatement.execute() returned '" + retVal + "'"); logger.debug("CallableStatement.getUpdateCount() returned " + updateCount); } //创建一个用于返回指向结果的集合 Map returnedResults = createResultsMap(); //如果CallableStatement执行的返回结果结果是结果集,或者 //CallableStatement执行的更新操作,数据库中有记录被更新 if (retVal || updateCount != -1) { //将存储过程调用结果提取成结果集集合 returnedResults.putAll(extractReturnedResults(cs, updateCountParameters, resultSetParameters, updateCount)); } //如果CallableStatement执行的返回结果不是结果集,且不是更新操作, //则将存储过程的输出参数提取为结果集 returnedResults.putAll(extractOutputParameters(cs, callParameters)); return returnedResults; } }); }
通过上面对 CallableStatement 相关处理方法的源码分析我们可以看到, execute 方法基本和 Statement 和 PreparedStatement 是相同的,不同之处在于 CallableStatement 是通过 jdbc 调用数据库的存储过程,对于输入输出参数的组装,以及返回结果的处理方面有些特殊处理。
6.JdbcTemplate 通过 DataSourceUtils 获取数据库连接:
JdbcTemplate 的 execute 方法中通过 DataSourceUtils .getConnection(getDataSource()); 获取数据库连接,下面我们就分析 DataSourceUtils 类获取数据库连接的实现过程:
//获取数据库连接的入口方法 public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { try { //通过doGetConnection方法来获取数据库连接 return doGetConnection(dataSource); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex); } } //获取数据库连接 public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); //把数据库连接放到事务管理器中管理 //通过TransactionSynchronizationManager中定义的线程局部 //变量(threadlocal)来和线程绑定数据库连接 ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); //如果TransactionSynchronizationManager中已有与当前线程绑定的数据库连接 if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { //从当前线程线程局部变量中获取数据库连接 conHolder.requested(); //如果当前线程局部变量中没有绑定数据库连接,则为当前线程局部变量设置数据库连接 if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(dataSource.getConnection()); } //直接返回TransactionSynchronizationManager线程局部变量中的数据库连接 return conHolder.getConnection(); } //如果TransactionSynchronizationManager中没有和当前线程绑定的数据 //库连接,则从Spring配置文件配置的数据源对象中获取数据库连接 logger.debug("Fetching JDBC Connection from DataSource"); Connection con = dataSource.getConnection(); //如果当前线程事务同步是Active的,即在注册之前可以直接使用,避免不必要 //的实例对象创建 if (TransactionSynchronizationManager.isSynchronizationActive()) { logger.debug("Registering transaction synchronization for JDBC Connection"); //在事务中使用同一个数据库连接做jdbc操作,当事务结束后,线程绑定对象 //将被同步移除 ConnectionHolder holderToUse = conHolder; //如果存放数据库连接的线程局部变量为null,则重新创建一个线程局部变量 if (holderToUse == null) { holderToUse = new ConnectionHolder(con); } //如果存放数据库连接的线程局部变量不为null,则将数据库连接存放到线程 //局部变量中 else { holderToUse.setConnection(con); } //请求数据库连接 holderToUse.requested(); //为当前线程注册事务同步 TransactionSynchronizationManager.registerSynchronization( new ConnectionSynchronization(holderToUse, dataSource)); //标记当前数据库连接为事务同步 holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { //将数据源绑定到当前线程局部变量 TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } return con; }
通过对 JdbcTemplate的源码分析,我们看到Spring只是将jdbc的一些常用操作封装,将通用的获取数据库连接、创建创建Statement、关闭资源释放连接等操作封装在不同种类的execute方法中,同时调用不同的回调处理Action来具体执行jdbc操作,对jdbc熟悉的人很容易看懂这部分源码,当然Spring还对jdbc进行了一些高级的封装和扩展,例如RowMapper将结果集转换为指定对象等,这部分有兴趣可以自己研究。