转载

.spring jdbctemplate源码跟踪

闲着没事,看看源码也是一种乐趣!

java操作数据库的基本步骤都是类似的:

1. 建立数据库连接

2. 创建Connection

3. 创建statement或者preparedStateement

4. 执行sql,返回ResultSet

5. 关闭resultSet

5.关闭statement

6.关闭Connection

Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。

1. 构造函数,三种形式

/**  * Construct a new JdbcTemplate for bean usage.  * <p>Note: The DataSource has to be set before using the instance.  * @see #setDataSource  */ public JdbcTemplate() { }  /**  * Construct a new JdbcTemplate, given a DataSource to obtain connections from.  * <p>Note: This will not trigger initialization of the exception translator.  * @param dataSource the JDBC DataSource to obtain connections from  */ public JdbcTemplate(DataSource dataSource) {     setDataSource(dataSource);     afterPropertiesSet(); }  /**  * Construct a new JdbcTemplate, given a DataSource to obtain connections from.  * <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator  * will be triggered.  * @param dataSource the JDBC DataSource to obtain connections from  * @param lazyInit whether to lazily initialize the SQLExceptionTranslator  */ public JdbcTemplate(DataSource dataSource, boolean lazyInit) {     setDataSource(dataSource);     setLazyInit(lazyInit);     afterPropertiesSet(); } 

一种思路:将datasource注入到JdbcTemplate。

2.获取Connection

//-------------------------------------------------------------------------  // Methods dealing with static SQL (java.sql.Statement)  //-------------------------------------------------------------------------   @Override  public <T> T execute(StatementCallback<T> action) throws DataAccessException {   Assert.notNull(action, "Callback object must not be null");      Connection con = DataSourceUtils.getConnection(getDataSource());   Statement stmt = null;   try {    Connection conToUse = con;    if (this.nativeJdbcExtractor != null &&      this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {     conToUse = this.nativeJdbcExtractor.getNativeConnection(con);    }    stmt = conToUse.createStatement();    applyStatementSettings(stmt);    Statement stmtToUse = stmt;    if (this.nativeJdbcExtractor != null) {     stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);    }    T result = action.doInStatement(stmtToUse);    handleWarnings(stmt);    return result;   }   catch (SQLException ex) {    // Release Connection early, to avoid potential connection pool deadlock    // in the case when the exception translator hasn't been initialized yet.    JdbcUtils.closeStatement(stmt);    stmt = null;    DataSourceUtils.releaseConnection(con, getDataSource());    con = null;    throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);   }   finally {    JdbcUtils.closeStatement(stmt);    DataSourceUtils.releaseConnection(con, getDataSource());   }  } 

Connection的获取方式:

2.1. 若是datasource直接使用jdbc而没有使用诸如c3p0,dbcp等第三方插件时则从下面的方法获取:

Connection con = DataSourceUtils.getConnection(getDataSource());
/**   * Actually obtain a JDBC Connection from the given DataSource.   * Same as {@link #getConnection}, but throwing the original SQLException.   * <p>Is aware of a corresponding Connection bound to the current thread, for example   * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread   * if transaction synchronization is active (e.g. if in a JTA transaction).   * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.   * @param dataSource the DataSource to obtain Connections from   * @return a JDBC Connection from the given DataSource   * @throws SQLException if thrown by JDBC methods   * @see #doReleaseConnection   */  public static Connection doGetConnection(DataSource dataSource) throws SQLException {   Assert.notNull(dataSource, "No DataSource specified");      ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);   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();   if (TransactionSynchronizationManager.isSynchronizationActive()) {    logger.debug("Registering transaction synchronization for JDBC Connection");    // Use same Connection for further JDBC actions within the transaction.    // Thread-bound object will get removed by synchronization at transaction completion.    ConnectionHolder holderToUse = conHolder;    if (holderToUse == null) {     holderToUse = new ConnectionHolder(con);    }    else {     holderToUse.setConnection(con);    }    holderToUse.requested();    TransactionSynchronizationManager.registerSynchronization(      new ConnectionSynchronization(holderToUse, dataSource));    holderToUse.setSynchronizedWithTransaction(true);    if (holderToUse != conHolder) {     TransactionSynchronizationManager.bindResource(dataSource, holderToUse);    }   }   return con;  } 

ConnectionHolder间接继承了ResourceHolder接口,ResourceHolder接口允许spring的事务基础可以在必要时检索和重置。通常我们只要继承ResourceHolderSupport即可。

2.2. 若使用了第三方插件时,则需要从插件中提取Connection。

conToUse = this.nativeJdbcExtractor.getNativeConnection(con);

第三方插件的关系如下:

.spring jdbctemplate源码跟踪

其具体方法如下:

/**   * Check for a ConnectionProxy chain, then delegate to doGetNativeConnection.   * <p>ConnectionProxy is used by Spring's TransactionAwareDataSourceProxy   * and LazyConnectionDataSourceProxy. The target connection behind it is   * typically one from a local connection pool, to be unwrapped by the   * doGetNativeConnection implementation of a concrete subclass.   * @see #doGetNativeConnection   * @see org.springframework.jdbc.datasource.ConnectionProxy   * @see org.springframework.jdbc.datasource.DataSourceUtils#getTargetConnection   * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy   * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy   */  @Override  public Connection getNativeConnection(Connection con) throws SQLException {   if (con == null) {    return null;   }   Connection targetCon = DataSourceUtils.getTargetConnection(con);   Connection nativeCon = doGetNativeConnection(targetCon);   if (nativeCon == targetCon) {    // We haven't received a different Connection, so we'll assume that there's    // some additional proxying going on. Let's check whether we get something    // different back from the DatabaseMetaData.getConnection() call.    DatabaseMetaData metaData = targetCon.getMetaData();    // The following check is only really there for mock Connections    // which might not carry a DatabaseMetaData instance.    if (metaData != null) {     Connection metaCon = metaData.getConnection();     if (metaCon != null && metaCon != targetCon) {      // We've received a different Connection there:      // Let's retry the native extraction process with it.      nativeCon = doGetNativeConnection(metaCon);     }    }   }   return nativeCon;  } 

具体的实现在其子类里面,以c3p0为例:

/**  * Retrieve the Connection via C3P0's {@code rawConnectionOperation} API,  * using the {@code getRawConnection} as callback to get access to the  * raw Connection (which is otherwise not directly supported by C3P0).  * @see #getRawConnection  */ @Override protected Connection doGetNativeConnection(Connection con) throws SQLException {  if (con instanceof C3P0ProxyConnection) {   C3P0ProxyConnection cpCon = (C3P0ProxyConnection) con;   try {    return (Connection) cpCon.rawConnectionOperation(      this.getRawConnectionMethod, null, new Object[] {C3P0ProxyConnection.RAW_CONNECTION});   }   catch (SQLException ex) {    throw ex;   }   catch (Exception ex) {    ReflectionUtils.handleReflectionException(ex);   }  }  return con; } 

调用C3P0 API获取Connection。

3. 创建Statement

stmt = conToUse.createStatement();             applyStatementSettings(stmt);             Statement stmtToUse = stmt;             if (this.nativeJdbcExtractor != null) {                 stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);             }

3.1 不使用第三方插件

/**  * Prepare the given JDBC Statement (or PreparedStatement or CallableStatement),  * applying statement settings such as fetch size, max rows, and query timeout.  * @param stmt the JDBC Statement to prepare  * @throws SQLException if thrown by JDBC API  * @see #setFetchSize  * @see #setMaxRows  * @see #setQueryTimeout  * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout  */ protected void applyStatementSettings(Statement stmt) throws SQLException {     int fetchSize = getFetchSize();     if (fetchSize > 0) {         stmt.setFetchSize(fetchSize);     }     int maxRows = getMaxRows();     if (maxRows > 0) {         stmt.setMaxRows(maxRows);     }     DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout()); } 

设置fetch size, max rows, and query timeout.其中过期时间在datasourceUtils中实现。

/**  * Apply the specified timeout - overridden by the current transaction timeout,  * if any - to the given JDBC Statement object.  * @param stmt the JDBC Statement object  * @param dataSource the DataSource that the Connection was obtained from  * @param timeout the timeout to apply (or 0 for no timeout outside of a transaction)  * @throws SQLException if thrown by JDBC methods  * @see java.sql.Statement#setQueryTimeout  */     public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {     Assert.notNull(stmt, "No Statement specified");     Assert.notNull(dataSource, "No DataSource specified");     ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);     if (holder != null && holder.hasTimeout()) {     // Remaining transaction timeout overrides specified value.     stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());     }     else if (timeout > 0) {     // No current transaction timeout -> apply specified value.     stmt.setQueryTimeout(timeout);     }     } 

3.2 第三方插件

以c3p0为例

/**  * Extracts the innermost delegate from the given Commons DBCP object.  * Falls back to the given object if no underlying object found.  * @param obj the Commons DBCP Connection/Statement/ResultSet  * @return the underlying native Connection/Statement/ResultSet  */ private static Object getInnermostDelegate(Object obj) throws SQLException {  if (obj == null) {   return null;  }  try {   Class<?> classToAnalyze = obj.getClass();   while (!Modifier.isPublic(classToAnalyze.getModifiers())) {    classToAnalyze = classToAnalyze.getSuperclass();    if (classToAnalyze == null) {     // No public provider class found -> fall back to given object.     return obj;    }   }   Method getInnermostDelegate = classToAnalyze.getMethod(GET_INNERMOST_DELEGATE_METHOD_NAME, (Class[]) null);   Object delegate = ReflectionUtils.invokeJdbcMethod(getInnermostDelegate, obj);   return (delegate != null ? delegate : obj);  }  catch (NoSuchMethodException ex) {   return obj;  }  catch (SecurityException ex) {   throw new IllegalStateException("Commons DBCP getInnermostDelegate method is not accessible: " + ex);  } } 

4. 执行statement并关闭之

T result = action.doInStatement(stmtToUse);
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {  @Override  public T doInStatement(Statement stmt) throws SQLException {   ResultSet rs = null;   try {    rs = stmt.executeQuery(sql);    ResultSet rsToUse = rs;    if (nativeJdbcExtractor != null) {     rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);    }    return rse.extractData(rsToUse);   }   finally {    JdbcUtils.closeResultSet(rs);   }  }  @Override  public String getSql() {   return sql;  } } 

4.1 不使用第三方插件

  rs = stmt.executeQuery(sql);

4.2 使用第三方插件

private static final String GET_INNERMOST_DELEGATE_METHOD_NAME = "getInnermostDelegate"; /**  * Extracts the innermost delegate from the given Commons DBCP object.  * Falls back to the given object if no underlying object found.  * @param obj the Commons DBCP Connection/Statement/ResultSet  * @return the underlying native Connection/Statement/ResultSet  */ private static Object getInnermostDelegate(Object obj) throws SQLException {  if (obj == null) {   return null;  }  try {   Class<?> classToAnalyze = obj.getClass();   while (!Modifier.isPublic(classToAnalyze.getModifiers())) {    classToAnalyze = classToAnalyze.getSuperclass();    if (classToAnalyze == null) {     // No public provider class found -> fall back to given object.     return obj;    }   }   Method getInnermostDelegate = classToAnalyze.getMethod(GET_INNERMOST_DELEGATE_METHOD_NAME, (Class[]) null);   Object delegate = ReflectionUtils.invokeJdbcMethod(getInnermostDelegate, obj);   return (delegate != null ? delegate : obj);  }  catch (NoSuchMethodException ex) {   return obj;  }  catch (SecurityException ex) {   throw new IllegalStateException("Commons DBCP getInnermostDelegate method is not accessible: " + ex);  } } 

4.3 提取数据

 @Override public List<T> extractData(ResultSet rs) throws SQLException {  List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>());  int rowNum = 0;  while (rs.next()) {   results.add(this.rowMapper.mapRow(rs, rowNum++));  }  return results; } 

或者

    @Override public SqlRowSet extractData(ResultSet rs) throws SQLException {     return createSqlRowSet(rs); }  /**  * Create a SqlRowSet that wraps the given ResultSet,  * representing its data in a disconnected fashion.  * <p>This implementation creates a Spring ResultSetWrappingSqlRowSet  * instance that wraps a standard JDBC CachedRowSet instance.  * Can be overridden to use a different implementation.  * @param rs the original ResultSet (connected)  * @return the disconnected SqlRowSet  * @throws SQLException if thrown by JDBC methods  * @see #newCachedRowSet  * @see org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet  */ protected SqlRowSet createSqlRowSet(ResultSet rs) throws SQLException {     CachedRowSet rowSet = newCachedRowSet();     rowSet.populate(rs);     return new ResultSetWrappingSqlRowSet(rowSet); } 

4.4 关闭ResultSet

/**  * Close the given JDBC ResultSet and ignore any thrown exception.  * This is useful for typical finally blocks in manual JDBC code.  * @param rs the JDBC ResultSet to close (may be {@code null})  */ public static void closeResultSet(ResultSet rs) {  if (rs != null) {   try {    rs.close();   }   catch (SQLException ex) {    logger.trace("Could not close JDBC ResultSet", ex);   }   catch (Throwable ex) {    // We don't trust the JDBC driver: It might throw RuntimeException or Error.    logger.trace("Unexpected exception on closing JDBC ResultSet", ex);   }  } } 

5. 释放connection

/**   * Close the given Connection, obtained from the given DataSource,   * if it is not managed externally (that is, not bound to the thread).   * @param con the Connection to close if necessary   * (if this is {@code null}, the call will be ignored)   * @param dataSource the DataSource that the Connection was obtained from   * (may be {@code null})   * @see #getConnection   */  public static void releaseConnection(Connection con, DataSource dataSource) {   try {    doReleaseConnection(con, dataSource);   }   catch (SQLException ex) {    logger.debug("Could not close JDBC Connection", ex);   }   catch (Throwable ex) {    logger.debug("Unexpected exception on closing JDBC Connection", ex);   }  }  /**   * Actually close the given Connection, obtained from the given DataSource.   * Same as {@link #releaseConnection}, but throwing the original SQLException.   * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.   * @param con the Connection to close if necessary   * (if this is {@code null}, the call will be ignored)   * @param dataSource the DataSource that the Connection was obtained from   * (may be {@code null})   * @throws SQLException if thrown by JDBC methods   * @see #doGetConnection   */  public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {   if (con == null) {    return;   }   if (dataSource != null) {    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);    if (conHolder != null && connectionEquals(conHolder, con)) {     // It's the transactional Connection: Don't close it.     conHolder.released();     return;    }   }   logger.debug("Returning JDBC Connection to DataSource");   doCloseConnection(con, dataSource);  } 

6.事务处理

6.1 事务开始

DataSourceTransactionManager 的doBegin()方法

/**  * This implementation sets the isolation level but ignores the timeout.  */ @Override protected void doBegin(Object transaction, TransactionDefinition definition) {  DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;  Connection con = null;  try {   if (txObject.getConnectionHolder() == null ||     txObject.getConnectionHolder().isSynchronizedWithTransaction()) {    Connection newCon = this.dataSource.getConnection();    if (logger.isDebugEnabled()) {     logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");    }    txObject.setConnectionHolder(new ConnectionHolder(newCon), true);   }   txObject.getConnectionHolder().setSynchronizedWithTransaction(true);   con = txObject.getConnectionHolder().getConnection();   Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);   txObject.setPreviousIsolationLevel(previousIsolationLevel);   // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,   // so we don't want to do it unnecessarily (for example if we've explicitly   // configured the connection pool to set it already).   if (con.getAutoCommit()) {    txObject.setMustRestoreAutoCommit(true);    if (logger.isDebugEnabled()) {     logger.debug("Switching JDBC Connection [" + con + "] to manual commit");    }    con.setAutoCommit(false);   }   txObject.getConnectionHolder().setTransactionActive(true);   int timeout = determineTimeout(definition);   if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {    txObject.getConnectionHolder().setTimeoutInSeconds(timeout);   }   // Bind the session holder to the thread.   if (txObject.isNewConnectionHolder()) {    TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());   }  }  catch (Throwable ex) {   if (txObject.isNewConnectionHolder()) {    DataSourceUtils.releaseConnection(con, this.dataSource);    txObject.setConnectionHolder(null, false);   }   throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);  } } 

6.2 事务提交

 @Override protected void doCommit(DefaultTransactionStatus status) {  DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();  Connection con = txObject.getConnectionHolder().getConnection();  if (status.isDebug()) {   logger.debug("Committing JDBC transaction on Connection [" + con + "]");  }  try {   con.commit();  }  catch (SQLException ex) {   throw new TransactionSystemException("Could not commit JDBC transaction", ex);  } } 

6.3 事务回滚

@Override protected void doRollback(DefaultTransactionStatus status) {  DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();  Connection con = txObject.getConnectionHolder().getConnection();  if (status.isDebug()) {   logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");  }  try {   con.rollback();  }  catch (SQLException ex) {   throw new TransactionSystemException("Could not roll back JDBC transaction", ex);  } } 

小结:

JdbcTemplate将我们使用的JDBC的流程封装起来,包括了异常的捕捉、SQL的执行、查询结果的转换等等。
spring大量使用Template Method模式来封装固定流程的动作,XXXTemplate等类别都是基于这种方式的实现。
除了大量使用Template Method来封装一些底层的操作细节,spring也大量使用callback方式类回调相关类别的方法以提供JDBC相关类别的功能,使传统的JDBC的使用者也能清楚了解spring所提供的相关封装类别方法的使用。
正文到此结束
Loading...