程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Spring源代碼解析(三):Spring JDBC

Spring源代碼解析(三):Spring JDBC

編輯:關於JAVA

下面我們看看Spring JDBC相關的實現,

在Spring中,JdbcTemplate是經常被使用的類來幫助用戶程序操作數據庫,在 JdbcTemplate為用戶程序提供了許多便利的數據庫操作方法,比如查詢,更新等,而且在 Spring中,有許多類似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看來這是 Rod.Johnson的慣用手法,一般而言這種Template中都是通過回調函數CallBack類的使用 來完成功能的,客戶需要在回調接口中實現自己需要的定制行為,比如使用客戶想要用的 SQL語句等。不過往往Spring通過這種回調函數的實現已經為我們提供了許多現成的方法 供客戶使用。一般來說回調函數的用法采用匿名類的方式來實現,比如:

代碼

JdbcTemplate = new JdbcTemplate(datasource);
jdbcTemplate.execute(new CallBack(){
       public CallbackInterfacedoInAction(){
        ......
        //用戶定義的代碼或者說Spring替我們實現的代碼
       }
}

在模板中嵌入的是需要客戶化的代碼,由Spring來作或者需要客戶程序親自動手完成 。下面讓我們具體看看在JdbcTemplate中的代碼是怎樣完成使命的,我們舉 JdbcTemplate.execute()為例,這個方法是在JdbcTemplate中被其他方法調用的基本方法 之一,客戶程序往往用這個方法來執行基本的SQL語句:

代碼

public Object execute(ConnectionCallback action) throws DataAccessException {
   //這裡得到數據庫聯接
   Connection con = DataSourceUtils.getConnection(getDataSource());
   try {
     Connection conToUse = con;
     //有些特殊的數據庫,需要我們使用特別的方法取得datasource
     if (this.nativeJdbcExtractor != null) {
       // Extract native JDBC Connection, castable to OracleConnection or the like.
       conToUse = this.nativeJdbcExtractor.getNativeConnection (con);
     }
     else {
       // Create close-suppressing Connection proxy, also preparing returned Statements.
       conToUse = createConnectionProxy(con);
     }
   //這裡調用的是傳遞進來的匿名類的方法,也就是用戶程序需要實現CallBack接 口的地方。
     return action.doInConnection(conToUse);
   }
   catch (SQLException ex) {
     //如果捕捉到數據庫異常,把數據庫聯接釋放,同時拋出一個經過Spring轉 換過的Spring數據庫異常,
     //我們知道,Spring做了一個有意義的工作是把這些數據庫異常統一到自己 的異常體系裡了。
     DataSourceUtils.releaseConnection(con, getDataSource());
     con = null;
     throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
   }
   finally {
     //最後不管怎樣都會把數據庫連接釋放
     DataSourceUtils.releaseConnection(con, getDataSource());
   }
}

對於JdbcTemplate中給出的其他方法,比如query,update,execute等的實現,我們看 看query():

代碼

public Object query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
     throws DataAccessException {
   ..........
   //這裡調用了我們上面看到的execute()基本方法,然而這裡的回調實現是Spring 為我們完成的查詢過程
   return execute(psc, new PreparedStatementCallback() {
     public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
       //准備查詢結果集
       ResultSet rs = null;
       try {
       //這裡配置SQL參數
         if (pss != null) {
           pss.setValues(ps);
         }
      //這裡執行的SQL查詢
         rs = ps.executeQuery();
         ResultSet rsToUse = rs;
         if (nativeJdbcExtractor != null) {
           rsToUse = nativeJdbcExtractor.getNativeResultSet (rs);
         }
     //返回需要的記錄集合
         return rse.extractData(rsToUse);
       }
       finally {
     //最後關閉查詢的紀錄集,對數據庫連接的釋放在execute()中釋放,就像我 們在上面分析的看到那樣。
         JdbcUtils.closeResultSet(rs);
         if (pss instanceof ParameterDisposer) {
           ((ParameterDisposer) pss).cleanupParameters();
         }
       }
     }
   });
}

輔助類DataSourceUtils來用來對數據庫連接進行管理的主要工具,比如打開和關閉數 據庫連接等基本操作:

代碼

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
  //把對數據庫連接放到事務管理裡面進行管理
   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");
     // 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;
}

那我們實際的DataSource對象是怎樣得到的?很清楚我們需要在上下文中進行配置: 它作為JdbcTemplate父類JdbcAccessor的屬性存在:

代碼

public abstract class JdbcAccessor implements InitializingBean {

   /** 這裡是我們依賴注入數據庫數據源的地方。*/
   private DataSource dataSource;

   /** Helper to translate SQL exceptions to DataAccessExceptions */
   private SQLExceptionTranslator exceptionTranslator;

   private boolean lazyInit = true;

   ........
}
 

而對於DataSource的緩沖池實現,我們通過定義Apache Jakarta Commons DBCP或者 C3P0提供的DataSource來完成,然後只要在上下文中配置好就可以使用了。從上面我們看 到JdbcTemplate提供了許多簡單查詢和更新功能,但是如果需要更高層次的抽象,以及更 面向對象的方法來訪問數據庫。Spring為我們提供了org.springframework.jdbc.object 包,這裡面包含了SqlQuery,SqlMappingQuery, SqlUpdate和StoredProcedure等類,這些 類都是Spring JDBC應用程序可以使用的主要類,但我們要注意使用這些類的時候,用戶 需要為他們配置好一個JdbcTemplate作為其基本的操作的實現。

比如說我們使用MappingSqlQuery來將表數據直接映射到一個對象集合 - 具體可以參 考書中的例子

1.我們需要建立DataSource和sql語句並建立持有這些對象的MappingSqlQuery對象

2.然後我們需要定義傳遞的SqlParameter,具體的實現我們在MappingSqlQuery的父類 RdbmsOperation中可以找到:

代碼

public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
  //如果聲明已經被編譯過,則該聲明無效
  if (isCompiled()) {
    throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
  }
  //這裡對參數值進行聲明定義
  this.declaredParameters.add(param);

而這個declareParameters維護的是一個列表:

代碼

/** List of SqlParameter objects */ 

private List declaredParameters = new LinkedList();

這個列表在以後compile的過程中會被使用。

3.然後用戶程序需要實現MappingSqlQuery的mapRow接口,將具體的ResultSet數據生 成我們需要的對象,這是我們迭代使用的方法。1,2,3步實際上為我們定義好了一個迭 代的基本單元作為操作模板。

4.在應用程序,我們直接調用execute()方法得到我們需要的對象列表,列表中的每一 個對象的數據來自於執行SQL語句得到記錄集的每一條記錄,事實上執行的execute在父類 SqlQuery中起作用:

代碼

public List executeByNamedParam(Map paramMap, Map context) throws DataAccessException {
   validateNamedParameters(paramMap);
   Object[] parameters = NamedParameterUtils.buildValueArray(getSql(), paramMap);
   RowMapper rowMapper = newRowMapper(parameters, context);
   String sqlToUse = NamedParameterUtils.substituteNamedParameters(getSql (), new MapSqlParameterSource(paramMap));
   //我們又看到了JdbcTemplate,這裡使用JdbcTemplate來完成對數據庫的查詢操作 ,所以我們說JdbcTemplate是基本的操作類。
   return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper);
}

在這裡我們可以看到template模式的精彩應用和對JdbcTemplate的靈活使用。通過使 用它,我們免去了手工迭代ResultSet並將其中的數據轉化為對象列表的重復過程。在這 裡我們只需要定義SQL語句和SqlParameter - 如果需要的話,往往SQL語句就常常能夠滿 足我們的要求了。這是靈活使用JdbcTemplate的一個很好的例子。

Spring還為其他數據庫操作提供了許多服務,比如使用SqlUpdate插入和更新數據庫, 使用UpdatableSqlQuery更新ResultSet,生成主鍵,調用存儲過程等。

書中還給出了對BLOB數據和CLOB數據進行數據庫操作的例子:

對BLOB數據的操作通過LobHander來完成,通過調用JdbcTemplate和RDBMS都可以進行 操作:

在JdbcTemplate中,具體的調用可以參考書中的例子 - 是通過以下調用起作用的:

代碼

public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
   return execute(new SimplePreparedStatementCreator(sql), action);
}

然後通過對實現PreparedStatementCallback接口的 AbstractLobCreatingPreparedStatementCallback的回調函數來完成:

代碼

public final Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
   LobCreator lobCreator = this.lobHandler.getLobCreator();
   try {
     //這是一個模板方法,具體需要由客戶程序實現
     setValues(ps, lobCreator);
     return new Integer(ps.executeUpdate());
   }
   finally {
     lobCreator.close();
   }
}
//定義的需要客戶程序實現的虛函數
protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator)
     throws SQLException, DataAccessException;

而我們注意到setValues()是一個需要實現的抽象方法,應用程序通過實現setValues 來定義自己的操作 - 在setValues中調用lobCreator.setBlobAsBinaryStrem()。讓我們 看看具體的BLOB操作在LobCreator是怎樣完成的,我們一般使用DefaultLobCreator作為 BLOB操作的驅動:

代碼

public void setBlobAsBinaryStream(
     PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
     throws SQLException {
   //通過JDBC來完成對BLOB數據的操作,對Oracle,Spring提供了OracleLobHandler 來支持BLOB操作。
   ps.setBinaryStream(paramIndex, binaryStream, contentLength);
   ........
}

上面提到的是零零碎碎的Spring JDBC使用的例子,可以看到使用Spring JDBC可以幫 助我們完成許多數據庫的操作。Spring對數據庫操作最基本的服務是通過JdbcTeamplate 和他常用的回調函數來實現的,在此之上,又提供了許多RMDB的操作來幫助我們更便利的 對數據庫的數據進行操作 - 注意這裡沒有引入向Hibernate這樣的O/R方案。對這些O/R方 案的支持,Spring由其他包來完成服務。

書中還提到關於execute和update方法之間的區別,update方法返回的是受影響的記錄 數目的一個計數,並且如果傳入參數的話,使用的是java.sql.PreparedStatement,而 execute方法總是使用 java.sql.Statement,不接受參數,而且他不返回受影響記錄的計 數,更適合於創建和丟棄表的語句,而update方法更適合於插入,更新和刪除操作,這也 是我們在使用時需要注意的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved