說說在 Spring 中如何使用資料來源(DBCP、C3P0、JNDI 等)
在 Spring 中,有以下三種方式來建立資料來源:
- 通過 JNDI 獲取應用伺服器中的資料來源;
- 在 Spring 容器中配置資料來源;
- 通過程式碼來建立資料來源,這種方式適用於無容器依賴的單元測試。
1 配置資料來源
Spring 在第三方依賴包中包含了 2 種資料來源的實現包一個是 Apache 的 DBCP;另一個是 C3P0。我們可以在 Spring 配置檔案中直接配置這些資料來源 。
1.1 DBCP
DBCP (Database Connection Pool)是一個依賴Jakartacommons-pool 物件池機制的資料庫連線池,所以在類路徑下還必須包括 commons-pool.jar。 下面是使用 DBCP 配置 MySql 資料來源的配置片段:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3309/db" /> <property name="username" value="root" /> <property name="password" value="xxxxxx" /> </bean>
BasicDataSource 提供了close()
方法用於關閉資料來源,所以必須設定destroy-method=”close”
, 以便 Spring 容器關閉時,能夠正常關閉資料來源。
除以上必須的資料來源屬性外,還有一些常用的屬性。
事務屬性:
屬性 | 預設值 | 說明 |
---|---|---|
defaultAutoCommit | true | 連線預設為 auto-commit 狀態。 |
defaultReadOnly | 驅動預設值 | 連線預設的 read-only 狀態 。如果沒有設定則 setReadOnly 方法將不會被呼叫。( 某些驅動不支援只讀模式 , 比如:Informix) |
defaultTransactionIsolation | 驅動預設值 | 連線預設的 TransactionIsolation 狀態。有這些值:NONE、READ_COMMITTED、READ_UNCOMMITTED、REPEATABLE_READ、SERIALIZABLE。 |
連線數相關屬性:
屬性 | 預設值 | 說明 |
---|---|---|
initialSize | 0 | 初始化連線數:連線池啟動時建立的初始化連線數量。 |
maxActive | 8 | 最大活動連線 : 連線池在同一時間內能夠分配的最大活動連線的數量。如果設定為非正數,則表示不限制。 |
maxIdle | 8 | 最大空閒連線 : 連線池中容許保持空閒狀態的最大連線數量 , 超過的空閒連線將被釋放 ,如果設定為負數,則表示不限制。 |
minIdle | 0 | 最小空閒連線 : 連線池中容許保持空閒狀態的最小連線數量 , 低於這個數量將建立新的連線 ,如果設定為 0,則表示不建立。 |
maxWait | 無限 | 最大等待時間 : 當沒有可用連線時 , 連線池等待連線被歸還的最大時間 ( 單位為毫秒 ),超出時間將丟擲異常 , 如果設定為 -1,則表示無限等待。 |
連線監測與維護相關屬性:
屬性 | 預設值 | 說明 |
---|---|---|
validationQuery | 無 | 配置 SQL 查詢語句 , 用於驗證從連線池取出的連線是否可用。如果指定 , 則查詢必須是一個 SQL SELECT,並且必須返回至少一行記錄。MySQL 中是 “select 1”;在 Oracle 中是 "select 1 from dual"。 |
testOnBorrow | true | 指明是否從連線池中取出連線之前進行檢測 , 如果檢測失敗 , 則從池中去除連線並嘗試取出另一個新的連線。注意 :設定為 true 後如果要生效,則 validationQuery 引數必須正確被設定。 |
testOnReturn | false | 指明是否在歸還到池中前進行檢測。注意 :與 testOnBorrow 一樣,設定為 true 後如果要生效,則 validationQuery 引數必須正確被設定。 |
testWhileIdle | false | 指明連線是否會被空閒連接回收器 ( 如果有 ) 所檢測。 如果檢測失敗 , 則連線將從池中被移除。注意 :設定為 true 後如果要生效,則 validationQuery 引數必須正確被設定。 |
timeBetweenEvictionRunsMillis | -1 | 空閒連接回收器執行緒執行的週期 , 以毫秒為單位。如果設定為非正數 , 則不執行空閒連接回收器執行緒。注意 :啟用該引數時,則 validationQuery 引數必須正確被設定。 |
numTestsPerEvictionRun | 3 | 在每次空閒連接回收器執行緒 ( 如果有 )執行時需要檢測的連線數量。 |
minEvictableIdleTimeMillis | 1000 * 60 * 30 | 連線在池中保持空閒而不被空閒連接回收器執行緒回收的最小時間值,以毫秒為單位。 |
快取相關屬性:
屬性 | 預設值 | 說明 |
---|---|---|
poolPreparedStatements | false | 開啟連線池的 prepared statement 功能設定為 true 後,所有的 CallableStatement 和 PreparedStatement 都會被快取起來。 |
maxOpenPreparedStatements | 不限制 | 能夠同時分配開啟的 statements 的最大數量。0 表示不限制。 |
連線洩露回收相關屬性:
屬性 | 預設值 | 說明 |
---|---|---|
removeAbandoned | false |
是否刪除洩露的連線。如果設定為 true,那麼那些可能存在洩露的連線會被刪除。假設 maxActive 為 10 個,活動連線為 8 個,空閒連線為 1 個,10-8-1=1
,那麼就會把刪除這個連線(會先檢測該活動連線未被使用的時間是否超過 removeAbandonedTimeout)。如果需要一個長連線操作,那麼 removeAbandoned 需要設定的長一些,否則正常使用的連線可能會被誤刪除。 |
removeAbandonedTimeout | 300 | 洩露的連線可以被刪除的時間段,單位為秒。 |
logAbandoned | false | 當 Statement 或連線被洩露時是否列印堆疊日誌 。 |
假設資料庫用的是 MySQL,那麼如果資料來源配置不當,將可能會發生經典的 “8 小時問題 ”。 原因是 MySQL 在預設情況下如果發現一個連線的空閒時間超過 8 小時,那麼會在資料庫端自動關閉這個連線 。 而資料來源並不知道這個連線已經被關閉了,所以當它將這個無用的連線返回給某個 DAO 時, DAO就會丟擲無法獲取 connection 的異常 。
DBCP 的 testOnBorrow 預設設定為 true,所以從連線池中取出連線之前會先進行檢測,因為不會發生 “8 小時問題 ”。 但如果每次取連線時都進行檢測,那麼在高併發應用下就會產生效能問題。
因此建議在高併發下,將 testOnBorrow 設定為 false;然後將 testWhileIdle 設定為 true,開啟空閒連接回收器;最後把 timeBetweenEvictionRunsMillis 的值設定為小於 8 小時,這樣那些被 MySQL 所關閉的空閒連線,就會被清除出去。這樣不僅解決了 “8 小時問題 ”,而且還保證了高效能 O(∩_∩)O哈哈~
注意:因為 MySQL 本身的 interactive-timeout(單位為 s)引數,可以設定空閒連線的過期時間,所以我們要想獲取到這個引數值,然後再設定 DBCP 的 timeBetweenEvictionRunsMillis 屬性值。
1.2 C3P0
C3P0 是一個開放原始碼的 JDBC 資料來源實現專案,它實現了 JDBC3 和 JDBC2 擴充套件規範說明的Connection和 Statement池 。
下面是使用 C3P0 配置 MySql 資料來源的配置片段:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver" /> <property name="jdbcUrl" value="jdbc:mysql://localhost:3309/db" /> <property name="use" value="xxx" /> <property name="password" value="xxxxxx" /> </bean>
C3P0 也提供了一個用於關閉資料來源的 close()方法,這樣我們就可以保證 Spring 容器被關閉時,能夠成功關閉資料來源 。
屬性 | 預設值 | 說明 |
---|---|---|
acquireIncrement | 當連線池中無空閒連線時, 一次性建立新連線的數量。 | |
acquireRetryAttempts | 30 | 在從資料庫獲取新連線失敗後,重複嘗試的次數。 |
acquireRetryDelay | 1000 | 嘗試獲取連線之間的間隔時間,單位為毫秒。 |
autoCommitOnClose | false | 連線關閉時,將所有未提交的操作回滾 。 |
automaticTestTable | null | 會建立一張名為 Test 的空表,並使用其自帶的查詢語句進行測試 。 如果定義了這個引數,那麼 preferredTestQuery 屬性 將被忽略 。 我們不能在這張 Test 表上進行任何操作,它僅為 C3P0 測試所用。 |
breakAfterAcquireFailure | false | 獲取連線失敗時,將會引起所有等待獲取連線的執行緒丟擲異常 。 但是資料來源仍有效保留,並在下次呼叫 getConnection()時繼續嘗試獲取連線 。 在嘗試獲取連線失敗後,該資料來源將申明已斷開並永久關閉。 |
checkoutTimeout | 0 | 當連線池中的連線用完時,客戶端呼叫 getConnection()後等待獲取新連線的時間,單位:毫秒。超時後將丟擲 SQLException 。設為 0 表示無限期等待 。 |
connectionTesterClassName | com.mchange.v2.C3P0.impl.DefaultConnectionTester | 通過實現 ConnectionTester 或 QueryConnectionTester 的類來測試連線,類名需設定為全限定名 。 |
idleConnectionTestPeriod | 0 | 隔多少秒,檢查連線池中的所有空閒連線。0 表示不檢查。 |
initialPoolSize | 3 | 初始化時建立的連線數,應在 minPoolSize 與 maxPoolSize 之間取值 。 |
maxIdleTime | 0 | 最大空閒時間,超過空閒時間的連線將會被丟棄 。 為 0 或負數則表示永不丟棄 。 |
maxPoolSize | 15 | 連線池中保留的最大連線數 。 |
maxStatements | 0 | JDBC 標準引數,用以控制資料來源內載入的 PreparedStatement 數量 。 但由於預快取的 Statement 屬於單個 Connection 而不是整個連線池 。 所以設定這個引數需要多方面的考慮,如果 maxStatements 與maxStatementsPerConnection 均為 0 ,則快取被關閉 。 |
maxStatementsPerConnection | 0 | 連線池內單個連線所擁有的最大快取 Statement 數 。 |
numHelperThreads | 3 | C3P0 是非同步操作的,緩慢的 JDBC 操作通過 HelperThreads 完成 。 通過多執行緒實現多個操作同時被執行,這樣可以有效地提升效能。 |
preferredTestQuery | null | 定義所有連線測試都執行的測試語句。在使用連線測試的情況下,這個引數能夠顯著地提高測試速度。測試的表必須在初始資料來源時就存在。 |
propertyCycle | 300 | 修改系統配置引數生效時長,單位為 s。 |
testConnectionOnCheckout | false | 因效能消耗大,所以請只在需要時開啟 。 如果設為 true 那麼在每個 connection 提交的時候都將校驗其有效性 。 建議使用 idleConnectionTestPeriod 或 automaticTestTable等方法來提升連線測試的效能 。 |
testConnectionOnCheckin | false | 如果設為 true,那麼在取得連線的同時將校驗其連線的有效性。 |
2 JNDI 資料來源
如果應用配置在高效能的應用伺服器(如 WebLogic 或 Websphere 等)上,我們可能更希望使用應用伺服器所提供的資料來源 。 應用伺服器的資料來源使用 JNDI 方式來供呼叫者使用, Spring 為此專門提供了引用 JNDI 資源的 JndiObjectFactoryBean 類 。 下面是一個簡單的配置:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean" p:jndiName="java:comp/env/jdbc/ds"/>
Spring2.0+ 為獲取 J2EE 資源提供了一個 jee 名稱空間,通過 jee 名稱空間,可以有效地簡化 J2EE 資源的引用:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd "> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/ds"/> </beans>
3Spring 資料來源實現類
Spring 本身也提供了一個簡單的資料來源實現類 DriverManagerDataSource,它位於org.springframework.jdbc.datasource
包中 。 這個類實現了javax.sql.DataSource
介面,但它並沒有提供池化連線機制,每次呼叫 getConnection()方法獲取新連線時,只是簡單地建立一個新的連線 。它不需要額外的依賴類,所以,這個資料來源類比較適合在單元測試中使用 。
Spring 資料來源實現類既可以通過配置直接使用,也可以在程式碼中例項化呼叫:
DriverManagerDataSource dataSource=new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/spring4"); dataSource.setUsername("root"); dataSource.setPassword(""); try { Connection connection=dataSource.getConnection(); if(connection.isClosed()){ System.out.println("連線已關閉"); }else{ System.out.println("連線已開啟"); } } catch (SQLException e) { e.printStackTrace(); }