spring源码分析之spring-jdbc模块详解
0 概述
Spring將替我們完成所有使用JDBC API進行開發的單調乏味的、底層細節處理工作。下表描述了哪些是spring幫助我們做好的,哪些是我們要做的。
| Action? | Spring | ?You |
| Define connection parameters. | ? | ?X |
| Open the connection.? | X | ? |
| Specify the SQL statement.? | ? | X |
| Declare parameters and provide parameter values | ? | X |
| Prepare and execute the statement. | X | ? |
| Set up the loop to iterate through the results (if any). | X | ? |
| Do the work for each iteration. | ? | X |
| Process any exception.? | X | ? |
| Handle transactions.? | X | ? |
| Close the connection,statement and resultset. | X | ? |
工作模式?
使用Spring進行基本的JDBC訪問數據庫有多種選擇。Spring至少提供了三種不同的工作模式:JdbcTemplate, 一個在Spring2.5中新提供的SimpleJdbc類能夠更好的處理數據庫元數據; 還有一種稱之為RDBMS Object的風格的面向對象封裝方式, 有點類似于JDO的查詢設計。 我們在這里簡要列舉你采取某一種工作方式的主要理由. 不過請注意, 即使你選擇了其中的一種工作模式, 你依然可以在你的代碼中混用其他任何一種模式以獲取其帶來的好處和優勢。 所有的工作模式都必須要求JDBC 2.0以上的數據庫驅動的支持, 其中一些高級的功能可能需要JDBC 3.0以上的數據庫驅動支持。?
JdbcTemplate - 這是經典的也是最常用的Spring對于JDBC訪問的方案。這也是最低級別的封裝, 其他的工作模式事實上在底層使用了JdbcTemplate作為其底層的實現基礎。JdbcTemplate在JDK 1.4以上的環境上工作得很好。?
NamedParameterJdbcTemplate - 對JdbcTemplate做了封裝,提供了更加便捷的基于命名參數的使用方式而不是傳統的JDBC所使用的“?”作為參數的占位符。這種方式在你需要為某個SQL指定許多個參數時,顯得更加直觀而易用。該特性必須工作在JDK 1.4以上。?
SimpleJdbcTemplate - 這個類結合了JdbcTemplate和NamedParameterJdbcTemplate的最常用的功能,同時它也利用了一些Java 5的特性所帶來的優勢,例如泛型、varargs和autoboxing等,從而提供了更加簡便的API訪問方式。需要工作在Java 5以上的環境中。?
SimpleJdbcInsert 和 SimpleJdbcCall - 這兩個類可以充分利用數據庫元數據的特性來簡化配置。通過使用這兩個類進行編程,你可以僅僅提供數據庫表名或者存儲過程的名稱以及一個Map作為參數。其中Map的key需要與數據庫表中的字段保持一致。這兩個類通常和SimpleJdbcTemplate配合使用。這兩個類需要工作在JDK 5以上,同時數據庫需要提供足夠的元數據信息。?
RDBMS 對象包括MappingSqlQuery, SqlUpdate and StoredProcedure - 這種方式允許你在初始化你的數據訪問層時創建可重用并且線程安全的對象。該對象在你定義了你的查詢語句,聲明查詢參數并編譯相應的Query之后被模型化。一旦模型化完成,任何執行函數就可以傳入不同的參數對之進行多次調用。這種方式需要工作在JDK 1.4以上。
?
1. 異常處理
? ?異常結構如下:
?SQLExceptionTranslator是一個接口,如果你需要在 SQLException和org.springframework.dao.DataAccessException之間作轉換,那么必須實現該接口。 轉換器類的實現可以采用一般通用的做法(比如使用JDBC的SQLState code),如果為了使轉換更準確,也可以進行定制(比如使用Oracle的error code)。?
SQLErrorCodeSQLExceptionTranslator是SQLExceptionTranslator的默認實現。 該實現使用指定數據庫廠商的error code,比采用SQLState更精確。轉換過程基于一個JavaBean(類型為SQLErrorCodes)中的error code。 這個JavaBean由SQLErrorCodesFactory工廠類創建,其中的內容來自于 “sql-error-codes.xml”配置文件。該文件中的數據庫廠商代碼基于 Database MetaData 信息中的DatabaseProductName,從而配合當前數據庫的使用。?
SQLErrorCodeSQLExceptionTranslator使用以下的匹配規則:?
首先檢查是否存在完成定制轉換的子類實現。通常SQLErrorCodeSQLExceptionTranslator 這個類可以作為一個具體類使用,不需要進行定制,那么這個規則將不適用。?
接著將SQLException的error code與錯誤代碼集中的error code進行匹配。 默認情況下錯誤代碼集將從SQLErrorCodesFactory取得。 錯誤代碼集來自classpath下的sql-error-codes.xml文件,它們將與數據庫metadata信息中的database name進行映射。?
使用fallback翻譯器。SQLStateSQLExceptionTranslator類是缺省的fallback翻譯器。
?
2. config模塊
??NamespaceHandler接口,DefaultBeanDefinitionDocumentReader使用該接口來處理在spring xml配置文件中自定義的命名空間。
在jdbc模塊,我們使用JdbcNamespaceHandler來處理jdbc配置的命名空間,其代碼如下:
public class JdbcNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {registerBeanDefinitionParser("embedded-database", new EmbeddedDatabaseBeanDefinitionParser());registerBeanDefinitionParser("initialize-database", new InitializeDatabaseBeanDefinitionParser());} }其中,
EmbeddedDatabaseBeanDefinitionParser繼承了AbstractBeanDefinitionParser,解析<embedded-database>元素,并使用EmbeddedDatabaseFactoryBean創建一個BeanDefinition。順便介紹一下用到的軟件包 org.w3c.dom。
軟件包 org.w3c.dom:為文檔對象模型 (DOM) 提供接口,該模型是 Java API for XML Processing 的組件 API。該 Document Object Model Level 2 Core API 允許程序動態訪問和更新文檔的內容和結構。
Attr:Attr 接口表示 Element 對象中的屬性。
CDATASection: CDATA 節用于轉義文本塊,該文本塊包含的字符如果不轉義則會被視為標記。
CharacterData: CharacterData 接口使用屬性集合和用于訪問 DOM 中字符數據的方法擴展節點。
Comment: 此接口繼承自 CharacterData 表示注釋的內容,即起始 '<!--' 和結束 '-->' 之間的所有字符。
Document: Document 接口表示整個 HTML 或 XML 文檔。
DocumentFragment: DocumentFragment 是“輕量級”或“最小”Document 對象。
DocumentType: 每個 Document 都有 doctype 屬性,該屬性的值可以為 null,也可以為 DocumentType 對象。
DOMConfiguration: 該 DOMConfiguration 接口表示文檔的配置,并維護一個可識別的參數表。
DOMError: DOMError 是一個描述錯誤的接口。
DOMErrorHandler: DOMErrorHandler 是在報告處理 XML 數據時發生的錯誤或在進行某些其他處理(如驗證文檔)時 DOM 實現可以調用的回調接口。
DOMImplementation: DOMImplementation 接口為執行獨立于文檔對象模型的任何特定實例的操作提供了許多方法。
DOMImplementationList: DOMImplementationList 接口提供對 DOM 實現的有序集合的抽象,沒有定義或約束如何實現此集合。
DOMImplementationSource: 此接口允許 DOM 實現程序根據請求的功能和版本提供一個或多個實現,如下所述。
DOMLocator: DOMLocator 是一個描述位置(如發生錯誤的位置)的接口。
DOMStringList: DOMStringList 接口提供對 DOMString 值的有序集合的抽象,沒有定義或約束此集合是如何實現的。
Element: Element 接口表示 HTML 或 XML 文檔中的一個元素。
Entity: 此接口表示在 XML 文檔中解析和未解析的已知實體。
EntityReference: EntityReference 節點可以用來在樹中表示實體引用。
NamedNodeMap: 實現 NamedNodeMap 接口的對象用于表示可以通過名稱訪問的節點的集合。
NameList NameList 接口提供對并行的名稱和名稱空間值對(可以為 null 值)的有序集合的抽象,無需定義或約束如何實現此集合。
Node: 該 Node 接口是整個文檔對象模型的主要數據類型。
NodeList: NodeList 接口提供對節點的有序集合的抽象,沒有定義或約束如何實現此集合。
Notation: 此接口表示在 DTD 中聲明的表示法。
ProcessingInstruction: ProcessingInstruction 接口表示“處理指令”,該指令作為一種在文檔的文本中保持特定于處理器的信息的方法在 XML 中使用。
Text: 該 Text 接口繼承自 CharacterData,并且表示 Element 或 Attr 的文本內容(在 XML 中稱為 字符數據)。
TypeInfo: TypeInfo 接口表示從 Element 或 Attr 節點引用的類型,用與文檔相關的模式指定。
UserDataHandler: 當使用 Node.setUserData() 將一個對象與節點上的鍵相關聯時,當克隆、導入或重命名該對象關聯的節點時應用程序可以提供調用的處理程序。
?3. core模塊
? ? ?3.1?NativeJdbcExtractor 從線程池中的封裝的對象中提取出本地的jdbc對象,其結構如下:
其實現原理如下(以c3po為例):
/*** 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的rawConnectionOperation?api來獲取Connection,使用getRawConnection的回調方法來獲取原生的Connection(C3P0不直接支持原生的Connection)。
NativeJdbcExtractorAdapter是NativeJdbcExtractor的一個簡單實現,它的getNativeConnection()方法檢查ConnectionProxy鏈,并且代理doGetNativeConnection方法。Spring的TransactionAwareDataSourceProxy和LazyConnectionDataSourceProxy使用ConnectionProxy。目標Connection置于本地連接池中,被子類實現的doGetNativeConnection的方法去掉封裝獲取到原生的Connection。其實現如下:
@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;}? 3.2 RowMapper
?
?
3.3 元數據metaData模塊
本節中spring應用到工廠模式,結合代碼可以更具體了解。
CallMetaDataProviderFactory創建CallMetaDataProvider的工廠類,其代碼如下:
/** List of supported database products for procedure calls */public static final List<String> supportedDatabaseProductsForProcedures = Arrays.asList("Apache Derby","DB2","MySQL","Microsoft SQL Server","Oracle","PostgreSQL","Sybase");/** List of supported database products for function calls */public static final List<String> supportedDatabaseProductsForFunctions = Arrays.asList("MySQL","Microsoft SQL Server","Oracle","PostgreSQL");/*** Create a CallMetaDataProvider based on the database metadata* @param dataSource used to retrieve metadata* @param context the class that holds configuration and metadata* @return instance of the CallMetaDataProvider implementation to be used*/static public CallMetaDataProvider createMetaDataProvider(DataSource dataSource, final CallMetaDataContext context) {try {return (CallMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, new DatabaseMetaDataCallback() {@Overridepublic Object processMetaData(DatabaseMetaData databaseMetaData) throws SQLException, MetaDataAccessException {String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData();if (context.isFunction()) {if (!supportedDatabaseProductsForFunctions.contains(databaseProductName)) {if (logger.isWarnEnabled()) {logger.warn(databaseProductName + " is not one of the databases fully supported for function calls " +"-- supported are: " + supportedDatabaseProductsForFunctions);}if (accessProcedureColumnMetaData) {logger.warn("Metadata processing disabled - you must specify all parameters explicitly");accessProcedureColumnMetaData = false;}}}else {if (!supportedDatabaseProductsForProcedures.contains(databaseProductName)) {if (logger.isWarnEnabled()) {logger.warn(databaseProductName + " is not one of the databases fully supported for procedure calls " +"-- supported are: " + supportedDatabaseProductsForProcedures);}if (accessProcedureColumnMetaData) {logger.warn("Metadata processing disabled - you must specify all parameters explicitly");accessProcedureColumnMetaData = false;}}}CallMetaDataProvider provider;if ("Oracle".equals(databaseProductName)) {provider = new OracleCallMetaDataProvider(databaseMetaData);}else if ("DB2".equals(databaseProductName)) {provider = new Db2CallMetaDataProvider((databaseMetaData));}else if ("Apache Derby".equals(databaseProductName)) {provider = new DerbyCallMetaDataProvider((databaseMetaData));}else if ("PostgreSQL".equals(databaseProductName)) {provider = new PostgresCallMetaDataProvider((databaseMetaData));}else if ("Sybase".equals(databaseProductName)) {provider = new SybaseCallMetaDataProvider((databaseMetaData));}else if ("Microsoft SQL Server".equals(databaseProductName)) {provider = new SqlServerCallMetaDataProvider((databaseMetaData));}else {provider = new GenericCallMetaDataProvider(databaseMetaData);}if (logger.isDebugEnabled()) {logger.debug("Using " + provider.getClass().getName());}provider.initializeWithMetaData(databaseMetaData);if (accessProcedureColumnMetaData) {provider.initializeWithProcedureColumnMetaData(databaseMetaData, context.getCatalogName(), context.getSchemaName(), context.getProcedureName());}return provider;}});}catch (MetaDataAccessException ex) {throw new DataAccessResourceFailureException("Error retreiving database metadata", ex);}}TableMetaDataProviderFactory創建TableMetaDataProvider工廠類,其創建過程如下:
/*** Create a TableMetaDataProvider based on the database metedata* @param dataSource used to retrieve metedata* @param context the class that holds configuration and metedata* @param nativeJdbcExtractor the NativeJdbcExtractor to be used* @return instance of the TableMetaDataProvider implementation to be used*/public static TableMetaDataProvider createMetaDataProvider(DataSource dataSource,final TableMetaDataContext context, final NativeJdbcExtractor nativeJdbcExtractor) {try {return (TableMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource,new DatabaseMetaDataCallback() {@Overridepublic Object processMetaData(DatabaseMetaData databaseMetaData) throws SQLException {String databaseProductName =JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());boolean accessTableColumnMetaData = context.isAccessTableColumnMetaData();TableMetaDataProvider provider;if ("Oracle".equals(databaseProductName)) {provider = new OracleTableMetaDataProvider(databaseMetaData,context.isOverrideIncludeSynonymsDefault());}else if ("HSQL Database Engine".equals(databaseProductName)) {provider = new HsqlTableMetaDataProvider(databaseMetaData);}else if ("PostgreSQL".equals(databaseProductName)) {provider = new PostgresTableMetaDataProvider(databaseMetaData);}else if ("Apache Derby".equals(databaseProductName)) {provider = new DerbyTableMetaDataProvider(databaseMetaData);}else {provider = new GenericTableMetaDataProvider(databaseMetaData);}if (nativeJdbcExtractor != null) {provider.setNativeJdbcExtractor(nativeJdbcExtractor);}if (logger.isDebugEnabled()) {logger.debug("Using " + provider.getClass().getSimpleName());}provider.initializeWithMetaData(databaseMetaData);if (accessTableColumnMetaData) {provider.initializeWithTableColumnMetaData(databaseMetaData, context.getCatalogName(),context.getSchemaName(), context.getTableName());}return provider;}});}catch (MetaDataAccessException ex) {throw new DataAccessResourceFailureException("Error retrieving database metadata", ex);}}?3.4?使用SqlParameterSource提供參數值?
使用Map來指定參數值有時候工作得非常好,但是這并不是最簡單的使用方式。Spring提供了一些其他的SqlParameterSource實現類來指定參數值。 我們首先可以看看BeanPropertySqlParameterSource類,這是一個非常簡便的指定參數的實現類,只要你有一個符合JavaBean規范的類就行了。它將使用其中的getter方法來獲取參數值。
SqlParameter 封裝了定義sql 參數的對象。
CallableStateMentCallback,PrePareStateMentCallback,StateMentCallback,ConnectionCallback回調類分別對應JdbcTemplate中的不同處理方法。
3.5 simple實現
4. DataSource
? ? ? spring通過DataSource獲取數據庫的連接。Datasource是jdbc 規范的一部分,它通過ConnectionFactory獲取。一個容器和框架可以在應用代碼層中隱藏連接池和事務管理。
? ? ?當使用spring的jdbc層,你可以通過JNDI來獲取DataSource,也可以通過你自己配置的第三方連接池實現來獲取。流行的第三方實現由apache Jakarta Commons dbcp和c3p0.
TransactionAwareDataSourceProxy作為目標DataSource的一個代理, 在對目標DataSource包裝的同時,還增加了Spring的事務管理能力, 在這一點上,這個類的功能非常像J2EE服務器所提供的事務化的JNDI DataSource。?
Note?
該類幾乎很少被用到,除非現有代碼在被調用的時候需要一個標準的 JDBC DataSource接口實現作為參數。 這種情況下,這個類可以使現有代碼參與Spring的事務管理。通常最好的做法是使用更高層的抽象 來對數據源進行管理,比如JdbcTemplate和DataSourceUtils等等。
注意:DriverManagerDataSource僅限于測試使用,因為它沒有提供池的功能,這會導致在多個請求獲取連接時性能很差。
5. object模塊
?
6.?JdbcTemplate是core包的核心類。
它替我們完成了資源的創建以及釋放工作,從而簡化了我們對JDBC的使用。 它還可以幫助我們避免一些常見的錯誤,比如忘記關閉數據庫連接。 JdbcTemplate將完成JDBC核心處理流程,比如SQL語句的創建、執行,而把SQL語句的生成以及查詢結果的提取工作留給我們的應用代碼。 它可以完成SQL查詢、更新以及調用存儲過程,可以對ResultSet進行遍歷并加以提取。 它還可以捕獲JDBC異常并將其轉換成org.springframework.dao包中定義的,通用的,信息更豐富的異常。?
使用JdbcTemplate進行編碼只需要根據明確定義的一組契約來實現回調接口。 PreparedStatementCreator回調接口通過給定的Connection創建一個PreparedStatement,包含SQL和任何相關的參數。 CallableStatementCreateor實現同樣的處理,只不過它創建的是CallableStatement。 RowCallbackHandler接口則從數據集的每一行中提取值。?
我們可以在DAO實現類中通過傳遞一個DataSource引用來完成JdbcTemplate的實例化,也可以在Spring的IoC容器中配置一個JdbcTemplate的bean并賦予DAO實現類作為一個實例。 需要注意的是DataSource在Spring的IoC容器中總是配制成一個bean,第一種情況下,DataSource bean將傳遞給service,第二種情況下DataSource bean傳遞給JdbcTemplate bean。
7.?NamedParameterJdbcTemplate類為JDBC操作增加了命名參數的特性支持,而不是傳統的使用('?')作為參數的占位符。NamedParameterJdbcTemplate類對JdbcTemplate類進行了封裝, 在底層,JdbcTemplate完成了多數的工作。
小結:
傳統的JDBC對數據庫的操作,有很多重復的代碼,這樣給程序員帶來了很多額外的工作量,Spring提供了JDBC模板很好的解決了這個問題,由于傳統的方法比較簡單,在這里不介紹了,直接說模板吧。 使用JDBC模板: Spring的JDBC框架能夠承擔資源管理和異常處理的工作。對于JDBC來說,Spring提供了3個模板類- JdbcTemplate:Spring里最基本的JDBC模板,利用JDBC和簡單的索引參數查詢提供對數據庫的簡單訪問。
- NamedParameterJdbcTemplate:能夠在執行查詢時把值綁定到SQL里的命名參數,而不是使用索引參數。
- SimpleJdbcTemplate:利用Java 5 的特性,比如自動裝箱、通用(generic)和可變參數類表來簡化JDBC模板的使用。
?參考文獻:
http://www.360doc.com/content/14/0625/22/834950_389749909.shtml
http://www.360doc.com/content/14/0625/23/834950_389759601.shtml
轉載于:https://www.cnblogs.com/davidwang456/p/4462351.html
總結
以上是生活随笔為你收集整理的spring源码分析之spring-jdbc模块详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: How to Setup Replica
- 下一篇: Getting started with