引自 :学习经典:Spring JDBC Framework
这里记录我对Spring JDBC框架的学习。由于Spring JDBC和我之前做的工作有很多共同之处,学习经典Framework的设计,取长补短,为我所用。
在这里,先佩服一下Rod JohnSon,他对数据库,JDBC的理解非常深。看Spring jdbc框架的设计和源代码,带给了我很多以前没有想到的东西。
我们知道,Spring JDBC的主要目标是为了简化JDBC的编程,方便我们构建健壮的应用程序。这里,它的一个基本设计理念,就是将JDBC编程中变化的和不变化的分开。
在JDBC中,什么是变化的?毫无疑问,SQL语句是变化的。那什么是不变化的?正确的使用JDBC的方式是不变化的。
先看一段代码。
- public List getAvailableSeatlds(DataSource ds, int performanceld,
- int seatType) throws ApplicationException {
- String sql = "SELECT seat_id AS id FROM available_seats " +
- "WHERE performance_id = ? AND price_band_id = ?";
- List seatlds = new LinkedList();
- Connection con = null;
- PreparedStatement ps = null;
- ResultSet rs = null;
- try {
- con = ds.getConnection(); //1。建立Connection
- ps = con.prepareStatement(sql); //2。创建preparedStatement
- ps.setlnt(1, performanceld); //3。设置ps的参数
- ps.setlnt(2, seatType);
- rs = ps.executeQuery(); //4.执行查询
- while (rs.next()) { //5.解析ResultSet
- int seatld = rs.getlnt(1);
- seatlds.add(new Integer(seatld));
- }
- rs.close(); //6.关闭资源,做好善后工作。rs,ps,connection
- ps.close();
- }
- catch (SQLException ex) {
- throw new ApplicationException ("Couldn't run query [" + sql + "]", ex);
- }
- finally {
- try {
- if (con != null)
- con.close(); //如果没有连接池的话,不要轻易关。connection属于耗费资源:)
- }
- catch (SQLException ex) {
- // Log and ignore
- }
- }
- return seatlds;
- }
从上面看,什么是不变的。首先,咱们这个使用JDBC的方式是良好的,正确的,也是不变的,也就是workflow不变。其次,这里头的很多操作是不变的,比如说:关闭资源,处理异常。
什么是变的?设置PreparedStament的参数是变化的,利用PreparedStatement做什么是变化的。
还有什么是变的?取得Connection可能是变化的,我们可以从ConnectionPool中取,也可以裸从Database取。
还有什么是变的?在主工作流之外,还可以对PreparedStament设置一些属性。比如fetchSize等。
还有什么是变的?解析ResultSet是变的。但是可以抽象,都是从结果集中取得你想要的东西。
很好。经过分析,我们会自然而然的想到Template设计模式。用模板方法来描述我们的工作流。对于固定的操作,我们会把它建模为一些帮助类,利用这些类来完成固定操作,这些操作在Template方法中被调用。
对于哪些可以变的方法。我们也发现,其实它要实现的功能是一样的。抽象起来,我们可以用一些接口来描述这些功能。比如说数据库连接管理的功能。
设计取决于我们考虑问题的深度,以及我们对过程划分的粒度。
下面,我们阅读Spring JDBC Template的代码吧。好好享受一下。下面几个接口是对变化的部分进行建模:)
- 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;
- }
- };
- public interface PreparedStatementSetter {
- void setValues(PreparedStatement ps) throws SQLException;
- }
- 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为外部类的变量。
- }
- };
- 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中将值取出来就OK了。
- }
- finally {
- //最后的善后工作还是需要做好的:) rs.close(),把ps的相关参数清除掉。
- JdbcUtils.closeResultSet(rs);
- if (pss instanceof ParameterDisposer) {
- ((ParameterDisposer) pss).cleanupParameters();
- }
- }
- }
- });
- }
Are you ready?看看execute()方法吧。
- 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设计模式被发扬广大了,我自己的程序中也主要是利用了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;
- }
- 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;
- }
- }
- 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了:)
- 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;
- }
- }
一路下来,真是很爽。Spring JDBC Framework真的可谓是深耕细作,这里只是管中窥豹了。类的职责设计得非常清除,同时有良好的设计模式支持,同时提供良好的编程接口,用户基本上只需要了结JdbcTemplate的API就可以了。厉害,厉害。