大家都使用過JDBCTEMPLATE的execute方法,execute作為數據庫操作的核心入口,將大多數數據庫操作相同的步驟統一封裝,而將個性化的操作使用參數PreparedStatementCallback回調。
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> 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; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } ps = psc.createPreparedStatement(conToUse); //應用用戶設定的輸入參數 applyStatementSettings(ps); PreparedStatement psToUse = ps; 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; throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex); } finally { if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); } }
以上方法對常用操作進行了封裝,包括如下幾項內容:
- 獲取數據庫連接
- 應用用戶設定的輸入參數
- 調用回調函數
- 警告處理
- 資源釋放
獲取數據庫連接
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(); } logger.debug("Fetching JDBC Connection from DataSource"); Connection con = dataSource.getConnection(); //當前線程支持同步 if (TransactionSynchronizationManager.isSynchronizationActive()) { logger.debug("Registering transaction synchronization for JDBC Connection"); //在事務中使用同一數據庫連接 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; }
在數據庫連接方面,Spring主要考慮的是關於事務方面的處理。基於事務處理的特殊性,Spring需要保證線程中的數據庫操作都是使用同一個事務連接。
應用用戶設定的輸入參數
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()); }
setFetchSize最主要是為了減少網絡交互次數設計的。訪問ResultSet時,如果它每次只從服務器讀取一行數據,則會產生大量的開銷。setFetchSize的意思是ResultSet會一次性從服務器上取得多少行數據回來,這樣在下次rs.next時,它可以直接從內存中獲取數據而不需要網絡交互,提高了效率。這個設置可能會被某些JDBC驅動忽略,而且設置過大也會造成內存的上升。setMaxRows將此Statement對象生成的所有ResultSet對象可以包含的最大行數設置為給定數。
調用回調函數
處理一些通用方法外的個性化處理,也就是PreparedStatementCallback類型的參數的doInPreparedStatement方法的回調。
T result = action.doInPreparedStatement(psToUse);
警告處理
protected void handleWarnings(Statement stmt) throws SQLException { //當設置為忽略警告時只嘗試打印日志 if (isIgnoreWarnings()) { if (logger.isDebugEnabled()) { //如果日志開啟的情況下下打印日志 SQLWarning warningToLog = stmt.getWarnings(); while (warningToLog != null) { logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" + warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]"); warningToLog = warningToLog.getNextWarning(); } } } else { handleWarnings(stmt.getWarnings()); } }
這里用到了一個類SQLWarning,SQLWarning提供關於數據庫訪問警告信息的異常。這些警告直接鏈接到導致報告警告的方法所在的對象。警告可以從Connection、Statement和ResultSet對象中獲得。試圖在已經關閉的連接上獲取警告將導致拋出異常。類似地,試圖在已經關閉的語句上或已經關閉的結果集上獲取警告也將導致拋出異常。注意,關閉語句時還會關閉它可能生成的結果集。
最常見的警告DataTruncation:DataTruncation直接繼承SQLWarning,由於某種原因意外地截斷數據值時會以DataTruncation 警告形式報告異常。對於警告的處理方式並不是直接拋出異常,出現警告很可能會出現數據錯誤,但是,並不一定會影響程序執行,所以用戶可以自己設置處理警告的方式,如默認的是忽略警告,當出現警告時只打印警告日志,而另一種方式只直接拋出異常。
資源釋放
數據庫的連接並不是直接調用了Connection的API中的close()方法。考慮到存在事務的情況,如果當前線程存在事務,那么說明在當前線程中存在共用數據庫的連接,這種情況下直接使用ConnectionHolder中的released方法進行連接數減一,而不是真正的釋放連接。
public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException { if (con == null) { return; } if (dataSource != null) { //當前線程存在事務的情況下說明存在共用數據庫連接 //直接使用ConnectionHolder中的released方法進行連接數減一而不是真正的釋放鏈接 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); }