spring源码解读之 JdbcTemplate源码
原文:https://blog.csdn.net/songjinbin/article/details/19857567
在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,有许多类似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯用手法,
所谓模板板式,就是在父类中定义算法的主要流程,而把一些个性化的步骤延迟到子类中去实现,父类始终控制着整个流程的主动权,子类只是辅助父类实现某些可定制的步骤。
我们用代码来说话吧:
首先,父类要是个抽象类:
Java代码
public abstract class TemplatePattern { //模板方法 public final void templateMethod(){ method1(); method2();//勾子方法 method3();//抽象方法 } private void method1(){ System.out.println("父类实现业务逻辑"); } public void method2(){ System.out.println("父类默认实现,子类可覆盖"); } protected abstract void method3();//子类负责实现业务逻辑 }
父类中有三个方法,分别是method1(),method2()和method3()。
method1()是私有方法,有且只能由父类实现逻辑,由于方法是private的,所以只能父类调用。
method2()是所谓的勾子方法。父类提供默认实现,如果子类觉得有必要定制,则可以覆盖父类的默认实现。
method3()是子类必须实现的方法,即制定的步骤。
由此可看出,算法的流程执行顺序是由父类掌控的,子类只能配合。
下面我们来写第一个子类:
Java代码
public class TemplatePatternImpl extends TemplatePattern { @Override protected void method3() { System.out.println("method3()在子类TemplatePatternImpl中实现了!!"); } }
这个子类只覆盖了必须覆盖的方法,我们来测试一下:
Java代码
TemplatePattern t1 = new TemplatePatternImpl(); t1.templateMethod();
在控制台中我们可以看到:
Java代码
父类实现业务逻辑
父类默认实现,子类可覆盖
method3()在子类TemplatePatternImpl中实现了!!
OK,我们来看看勾子方法的使用:
定义第2个子类,实现勾子方法:
Java代码
public class TemplatePatternImpl2 extends TemplatePattern { @Override protected void method3() { System.out.println("method3()在子类TemplatePatternImpl2中实现了!!"); } /* (non-Javadoc) * @see com.jak.pattern.template.example.TemplatePattern#method2() */ @Override public void method2() { System.out.println("子类TemplatePatternImpl2覆盖了父类的method2()方法!!"); } }
来测试一下:
Java代码
TemplatePattern t2 = new TemplatePatternImpl2(); t2.templateMethod();
我们看控制台:
Java代码
父类实现业务逻辑
子类TemplatePatternImpl2覆盖了父类的method2()方法!!
method3()在子类TemplatePatternImpl2中实现了!!
OK,经典的模板模式回顾完了(大家不要拍砖哦~~~~~~~~~~)
接下来,我们回到正题,自己模仿spring动手写一个基于模板模式和回调的jdbcTemplate。
回顾一下,spring为什么要封装JDBC API,对外提供jdbcTemplate呢(不要仍鸡蛋啊¥·%¥#%)
话说SUN的JDBC API也算是经典了,曾经在某个年代折服了一批人。但随着历史的发展,纯粹的JDBC API已经过于底层,而且不易控制,由开发人员直接接触JDBC API,会造成不可预知的风险。还有,数据连接缓存池的发展,也不可能让开发人员去手工获取JDBC了。
好了,我们来看一段曾经堪称经典的JDBC API代码吧:
Java代码
public List<User> query() { List<User> userList = new ArrayList<User>(); String sql = "select * from User"; Connection con = null; PreparedStatement pst = null; ResultSet rs = null; try { con = HsqldbUtil.getConnection(); pst = con.prepareStatement(sql); rs = pst.executeQuery(); User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } } catch (SQLException e) { e.printStackTrace(); }finally{ if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } try { pst.close(); } catch (SQLException e) { e.printStackTrace(); } try { if(!con.isClosed()){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } catch (SQLException e) { e.printStackTrace(); } } return userList; }
上面的代码要若干年前可能是一段十分经典的,还可能被作为example被推广。但时过境迁,倘若哪位程序员现在再在自己的程序中出现以上代码,不是说明该公司的开发框架管理混乱,就说明这位程序员水平太“高”了。
我们试想,一个简单的查询,就要做这么一大堆事情,而且还要处理异常,我们不防来梳理一下:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,而且还要考虑各种异常
6、.....
啊~~~~ 我快要晕了,在面向对象编程的年代里,这样的代码简直不能上人容忍。试想,上面我们只是做了一张表的查询,如果我们要做第2张表,第3张表呢,又是一堆重复的代码:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,而且还要考虑各种异常
6、.....
这时候,使用模板模式的时机到了!!!
通过观察我们发现上面步骤中大多数都是重复的,可复用的,只有在遍历ResultSet并封装成集合的这一步骤是可定制的,因为每张表都映射不同的java bean。这部分代码是没有办法复用的,只能定制。那就让我们用一个抽象的父类把它们封装一下吧:
Java代码
public abstract class JdbcTemplate { //template method public final Object execute(String sql) throws SQLException{ Connection con = HsqldbUtil.getConnection(); Statement stmt = null; try { stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(sql); Object result = doInStatement(rs);//abstract method return result; } catch (SQLException ex) { ex.printStackTrace(); throw ex; } finally { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if(!con.isClosed()){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } catch (SQLException e) { e.printStackTrace(); } } } //implements in subclass protected abstract Object doInStatement(ResultSet rs); }
在上面这个抽象类中,封装了SUN JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中,由子类负责实现。
好,我们来定义一个子类,并继承上面的父类:
Java代码
public class JdbcTemplateUserImpl extends JdbcTemplate { @Override protected Object doInStatement(ResultSet rs) { List<User> userList = new ArrayList<User>(); try { User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } return userList; } catch (SQLException e) { e.printStackTrace(); return null; } } }
由代码可见,我们在doInStatement()方法中,对ResultSet进行了遍历,最后并返回。
有人可能要问:我如何获取ResultSet 并传给doInStatement()方法啊??呵呵,问这个问题的大多是新手。因为此方法不是由子类调用的,而是由父类调用,并把ResultSet传递给子类的。我们来看一下测试代码:
Java代码
String sql = "select * from User"; JdbcTemplate jt = new JdbcTemplateUserImpl(); List<User> userList = (List<User>) jt.execute(sql);
就是这么简单!!
文章至此仿佛告一段落,莫急!不防让我们更深入一些...
试想,如果我每次用jdbcTemplate时,都要继承一下上面的父类,是不是有些不方面呢?
那就让我们甩掉abstract这顶帽子吧,这时,就该callback(回调)上场了
所谓回调,就是方法参数中传递一个接口,父类在调用此方法时,必须调用方法中传递的接口的实现类。
那我们就来把上面的代码改造一下,改用回调实现吧:
首先,我们来定义一个回调接口:
Java代码
public interface StatementCallback { Object doInStatement(Statement stmt) throws SQLException; }
这时候,我们就要方法的签名改一下了:
Java代码
private final Object execute(StatementCallback action) throws SQLException
里面的获取数据方式也要做如下修改:
Java代码
Object result = action.doInStatement(stmt);//abstract method
为了看着顺眼,我们来给他封装一层吧:
Java代码
public Object query(StatementCallback stmt) throws SQLException{ return execute(stmt); }
OK,大功告成!
我们来写一个测试类Test.java测试一下吧:
这时候,访问有两种方式,一种是内部类的方式,一种是匿名方式。
先来看看内部类的方式:
Java代码
//内部类方式 public Object query(final String sql) throws SQLException { class QueryStatementCallback implements StatementCallback { public Object doInStatement(Statement stmt) throws SQLException { ResultSet rs = stmt.executeQuery(sql); List<User> userList = new ArrayList<User>(); User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } return userList; } } JdbcTemplate jt = new JdbcTemplate(); return jt.query(new QueryStatementCallback()); }
在调用jdbcTemplate.query()方法时,传一个StatementCallBack()的实例过去,也就是我们的内部类。
再来看看匿名方式:
Java代码
//匿名类方式 public Object query2(final String sql) throws Exception{ JdbcTemplate jt = new JdbcTemplate(); return jt.query(new StatementCallback() { public Object doInStatement(Statement stmt) throws SQLException { ResultSet rs = stmt.executeQuery(sql); List<User> userList = new ArrayList<User>(); User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } return userList; } }); }
相比之下,这种方法更为简洁。
为什么spring不用传统的模板方法,而加之以Callback进行配合呢?
试想,如果父类中有10个抽象方法,而继承它的所有子类则要将这10个抽象方法全部实现,子类显得非常臃肿。而有时候某个子类只需要定制父类中的某一个方法该怎么办呢?这个时候就要用到Callback回调了。
最后的源码为:
package com.jak.pattern.template.callbacktemplate; import java.sql.SQLException; import java.sql.Statement; public interface StatementCallback { Object doInStatement(Statement stmt) throws SQLException; } public class JdbcTemplate { //template method private final Object execute(StatementCallback action) throws SQLException{ Connection con = HsqldbUtil.getConnection(); Statement stmt = null; try { stmt = con.createStatement(); Object result = action.doInStatement(stmt);//abstract method return result; } catch (SQLException ex) { ex.printStackTrace(); throw ex; } finally { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if(!con.isClosed()){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } catch (SQLException e) { e.printStackTrace(); } } } public Object query(StatementCallback stmt) throws SQLException{ return execute(stmt); } }
//调用测试类
public class Test { //内部类方式 public Object query(final String sql) throws SQLException { class QueryStatementCallback implements StatementCallback { public Object doInStatement(Statement stmt) throws SQLException { ResultSet rs = stmt.executeQuery(sql); List<User> userList = new ArrayList<User>(); User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } return userList; } } JdbcTemplate jt = new JdbcTemplate(); return jt.query(new QueryStatementCallback()); } //匿名类方式 public Object query2(final String sql) throws Exception{ JdbcTemplate jt = new JdbcTemplate(); return jt.query(new StatementCallback() { public Object doInStatement(Statement stmt) throws SQLException { ResultSet rs = stmt.executeQuery(sql); List<User> userList = new ArrayList<User>(); User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } return userList; } }); } public static void main(String[] args) throws Exception { String sql = "select * from User"; Test t = new Test(); List<User> userList = (List<User>) t.query(sql); List<User> userList2 = (List<User>) t.query2(sql); System.out.println(userList); System.out.println(userList2); } }
言归正传,有了上面的基础后,我们正式开始阅读源码:
下面几个接口是对变化的部分进行建模
接口:创建PreparedStatement。根据Connection来创建PreparedStatement。
public interface PreparedStatementCreator { PreparedStatement createPreparedStatement (Connection conn) throws SQLException; } 使用方法就是: PreparedStatementCreator psc = new PreparedStatementCreator() { public PreparedStatement createPreparedStatement (Connection conn) throws SQLException { PreparedStatement ps = conn. prepareStatement ( "SELECT seat_id AS id FROM available_seats WHERE " + "performance_id = ? AND price_band_id = ?"); ps.setInt(1, performanceId); ps.setInt(2, seatType); return ps; } };
给PreparedStatement设置参数。是对PreparedStatmentCreator的设置ps值的一个补充。
public interface PreparedStatementSetter { void setValues(PreparedStatement ps) throws SQLException; }
对ResultSet进行处理。还有具体的子类。
public interface RowCallbackHandler { void processRow(ResultSet rs) throws SQLException; }
使用方式:
RowCallbackHandler rch = new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { int seatId = rs.getInt(1) ; list.add(new Integer (seatId) );//典型的inner class的应用,list为外部类的变量。 } };
和上面的RowCallbackHandler类似。
public interface ResultSetExtractor { Object extractData(ResultSet rs) throws SQLException, DataAccessException; }
下面是JdbcTemplate中提供的模板方法。该方法完成对数据库的查询:)。
这个execute()方法非常关键。
public Object query( PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse) throws DataAccessException { Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { String sql = getSql(psc); //取得不变的SQL部分。 logger.debug("Executing SQL query" + (sql != null ? " [" + sql + "]" : "")); } return execute(psc, new PreparedStatementCallback() { public Object doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { if (pss != null) { pss.setValues(ps);//就是给ps来设置参数用的。ps.setInt(1, 0); } rs = ps.executeQuery();//执行查询 ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); // ResultSetExtractor从ResultSet中将值取出List。 } finally { //最后的善后工作还是需要做好的:) rs.close(),把ps的相关参数清除掉。 JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }); }
看看execute()方法吧。
java 代码
public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null"); Assert.notNull(action, "Callback object must not be null"); //取得数据库的连接 Connection con = DataSourceUtils.getConnection(getDataSource()); PreparedStatement ps = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } //创建PreparedStatement ps = psc.createPreparedStatement(conToUse); applyStatementSettings(ps);//这个方法是设置ps的一些属性,我平时不用,Spring框架倒是考虑得相当全的说。 PreparedStatement psToUse = ps; if (this.nativeJdbcExtractor != null) { psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps); } //调用Callback来完成PreparedStatement的设值。就是调用上面的doInPreparedStatement来使用ps。 Object result = action.doInPreparedStatement(psToUse); SQLWarning warning = ps.getWarnings(); throwExceptionOnWarningIfNotIgnoringWarnings(warning); return result; } //如果有错误的话,那么就开始ps.close(), connection.close(); catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } String sql = getSql(psc); psc = null; JdbcUtils.closeStatement(ps); //就是ps.close(); ps = null; DataSourceUtils.releaseConnection(con, getDataSource()); / con = null; throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex); }
//不管怎么样,ps.close(), Connection.close()吧,当然这里是releaseConnection。在我的程序中,Connection只有一个,没有ConnectionPool,当然不会去close Connection。一般来讲,如果没有Connection的线程池的话,我们肯定也不会经常的关闭Connection,得到Connection。毕竟这个东西非常耗费资源。
finally { if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); } }
JdbcTemplate完成了负责的操作,客户只需要调用query()就可以完成查询操作了。当然,JdbcTemplate会实现很多带其它参数的方法,以方便你的使用。Template设计模式被发扬广大了。
DataSourceUtils:这个专门用于管理数据库Connection的类。
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { try { return doGetConnection(dataSource); ~~~~~~ //这个方法很舒服,Spring Framework中到处有这样的方法。为什么要委派到这个动作方法? } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex); } }
这里的doGetConnection就稍微复杂一点了。但是如果没有事务同步管理器的话,那就比较简单。
只是在Connection上多了一个ConnecionHolder类用于持有Connection,实现ConnectionPool的一点小功能。
public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); ~~~~~//Connection的持有器。通过持有器得到Connection。 if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(dataSource.getConnection()); } return conHolder.getConnection(); } // Else we either got no holder or an empty thread-bound holder here. logger.debug("Fetching JDBC Connection from DataSource"); Connection con = dataSource.getConnection(); …… return con; } ConnectionHolder:Connection的持有器。通过ConnectionHandler来完成对Connection的操作:) 典型的委派。 public class ConnectionHolder extends ResourceHolderSupport { private Connection currentConnection; //当前的Connection private ConnectionHandle connectionHandle; //Connection的处理器,因此可以通过该类完成对connection的管理。 public ConnectionHolder(Connection connection) { this.connectionHandle = new SimpleConnectionHandle(connection); } public ConnectionHolder(ConnectionHandle connectionHandle) { Assert.notNull(connectionHandle, "ConnectionHandle must not be null"); this.connectionHandle = connectionHandle; } public Connection getConnection() { Assert.notNull(this.connectionHandle, "Active Connection is required"); if (this.currentConnection == null) { this.currentConnection = this.connectionHandle.getConnection(); } return this.currentConnection; } public void released() { super.released(); if (this.currentConnection != null) { this.connectionHandle.releaseConnection(this.currentConnection); this.currentConnection = null; } } connectionHandle 的接口太纯粹了。但是我觉得这个设计太过于细致了:) public interface ConnectionHandle { /** * Fetch the JDBC Connection that this handle refers to. */ Connection getConnection(); /** * Release the JDBC Connection that this handle refers to. * @param con the JDBC Connection to release */ void releaseConnection(Connection con); }
最后看一下SimpleConnectionHandle,这个ConnectionHandle的简单实现类。就只有一个Connection可管理。如果有多个Connection可管理的话,这里就是ConnectionPool了:)
java 代码
public class SimpleConnectionHandle implements ConnectionHandle { private final Connection connection; /** * Create a new SimpleConnectionHandle for the given Connection. * @param connection the JDBC Connection */ public SimpleConnectionHandle(Connection connection) { Assert.notNull(connection, "Connection must not be null"); this.connection = connection; } /** * Return the specified Connection as-is. */ public Connection getConnection() { return connection; } /** * This implementation is empty, as we're using a standard * Connection handle that does not have to be released. */ public void releaseConnection(Connection con) { } public String toString() { return "SimpleConnectionHandle: " + this.connection; } }