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)//------------------------------------------------------------------------- @Overridepublic <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);?第三方插件的關系如下:
其具體方法如下:
/*** 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*/@Overridepublic 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*/@Overrideprotected 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 {@Overridepublic 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);}}@Overridepublic 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 提取數據
@Overridepublic 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;}或者
@Overridepublic 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.*/@Overrideprotected 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 事務提交
@Overrideprotected 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 事務回滾
@Overrideprotected 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);}}事務過程:
Spring 對DataSource進行事務管理的關鍵在于ConnectionHolder和TransactionSynchronizationManager。
0.先從TransactionSynchronizationManager中嘗試獲取連接
1.如果前一步失敗則在每個線程上,對每個DataSouce只創建一個Connection
2.這個Connection用ConnectionHolder包裝起來,由TransactionSynchronizationManager管理
3.再次請求同一個連接的時候,從TransactionSynchronizationManager返回已經創建的ConnectionHolder,然后調用ConnectionHolder的request將引用計數+1
4.釋放連接時要調用ConnectionHolder的released,將引用計數-1
5.當事物完成后,將ConnectionHolder從TransactionSynchronizationManager中解除。當誰都不用,這個連接被close
以上所有都是可以調用DataSourceUtils化簡代碼,而JdbcTemplate又是調用DataSourceUtils的。所以在 Spring文檔中要求盡量首先使用JdbcTemplate,其次是用DataSourceUtils來獲取和釋放連接。至于 TransactionAwareDataSourceProxy,那是下策的下策。不過可以將Spring事務管理和遺留代碼無縫集成。
所以如某位朋友說要使用Spring的事務管理,但是又不想用JdbcTemplate,那么可以考慮TransactionAwareDataSourceProxy。這個類是原來DataSource的代理。
其次,想使用Spring事物,又不想對Spring進行依賴是不可能的。與其試圖自己模擬DataSourceUtils,不如直接使用現成的。
小結:
JdbcTemplate將我們使用的JDBC的流程封裝起來,包括了異常的捕捉、SQL的執行、查詢結果的轉換等等。spring大量使用Template Method模式來封裝固定流程的動作,XXXTemplate等類別都是基于這種方式的實現。
除了大量使用Template Method來封裝一些底層的操作細節,spring也大量使用callback方式類回調相關類別的方法以提供JDBC相關類別的功能,使傳統的JDBC的使用者也能清楚了解spring所提供的相關封裝類別方法的使用。
?參考文獻:
【1】http://blog.sina.com.cn/s/blog_53dd74430100haaj.html
轉載于:https://www.cnblogs.com/davidwang456/p/4542409.html
總結
以上是生活随笔為你收集整理的spring jdbctemplate源码跟踪的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring data redis使用示
- 下一篇: LMAX Disruptor – Hig