JDBC基礎知識
1、採用JDBC訪問數據庫的基本步驟:
A.載入JDBC驅動程序
B.定義鏈接URL
C.創建鏈接
D.建立Statement對象
E.執行查詢或更新
F.結果處理
G.關閉鏈接
2、載入JDBC驅動程序:
1.爲了使代碼儘量地靈活,咱們要避免對類名的引用進行硬編碼(hard-coding),所以咱們能夠採用從Properties文件中載入驅動程序的方法,也可使用在服務器中配置數據源(DataSource)的方法來避免在代碼中硬編碼
2.在開發過程當中要保證CLASSPATH設定中包括驅動程序JAR文件所在的路徑。在WEB服務
器上部署時要將JAR文件放在Web應用的WEB-INF/lib目錄下。若是多個Web應用使用相同的數據庫驅動程序能夠將JAR文件放置在服務器使用的公共目錄<%CATALINA_HOME%>/common/lib中
3、定義鏈接URL:
載入JDBC驅動程序以後,必須指定數據庫服務器位置。指向數據庫的URL所使用的協議是:
jdbc:子協議,而且載入服務器的主機名、端口、數據庫名(或引用)。如:Oracle 的鏈接URL:
jdbc:oracle:thin:@192.168.0.71:1521:UMV2
jdbc:oracle:採用Oracle驅動程序
thin:指鏈接服務器所採用的模式
@192.168.0.71:服務器的地址
1521:服務器的監聽端口
UMV2:數據庫名
4、創建鏈接:
1.一個數據庫鏈接(Connection)能夠經過其自身的getMetaData()來獲取它的自身信息
2.默認狀況下一個數據庫的鏈接是自動提交模式的(auto-commit),也就是說每當一個SQL語句
被執行後其改變結果都會被自動提交,若是auto-commit模式被關閉,那麼方法commit()必須被顯式調用以提交改變結果,不然的話全部對數據庫操做的結果都不會被保存
5、建立Statement對象:
在 同一時間下,每一個Statement對象只能打開一個ResultSet對象。因此,假若有兩個一樣結果的結果集在交叉訪問,那麼這兩個結果集一定爲兩個 不一樣的Statement對象所建立。若是在打開一個新的結果集的時候存在一個已經打開的結果集,則這個已經存在的結果集會被隱式的關閉
6、執行查詢或更新:
在Statement對象中能夠執行以下的操做:
A.查詢操做:executeQuery(SQL語句) B.維護操做:executeUpdate(SQL語句)
C.批處理操做:executeBath()
7、結果處理:
1.ResultSet中行的第一列索引爲1,而非0,訪問ResultSet中的數據時要使用列名,而非索引
但要注意使用列名做爲查詢條件是大小寫敏感的。
2.JDBC1.0中,咱們只能在ResultSet中向前移動;在JDBC2.0中,咱們能夠在ResultSet中向
下(next)或向上(previous)移動,一樣也能夠移到特定的行(relative,absolute)
3.默認狀況下ResultSet是不可更新的,且只能向前移動。下面的代碼顯示瞭如何建立一個可滾動的、對更新敏感的ResultSet
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE2");
// rs will be scrollable, will not show changes made by others,
// and will be updatable
4.ResultSet和ResultSetMetaData沒有直接提供方法返回查詢所返回的行數。然而,在JDBC
2.0中,能夠經過調用last()方法將遊標定位到ResultSet的最後一行,而後調用getRow()方
法獲取當前的行號。在JDBC1.0中,肯定行數的唯一方式是重複調用ResultSet的next()方法,
直到它返回false爲至
8、關閉鏈接:
在關閉數據庫鏈接時應該以ResultSet、Statement、Connection的順序進行
JDBC-PreparedStatement(預備語句)
1、PreparedStatement(預備語句)的建立:
首 先按照標準的格式建立參數化語句,在實際使用以前發送參數到數據庫進行編譯。用問號表示語句中應該爲具體的值所替換的位置。每次使用預備語句時,只須要使 用相應的setXxx調用,替換語句中標記出來的參數。而後就能夠和常規的語句同樣,使用executeQuery或 execute/executeUpdate修改表中的數據。例如:
Connection connection = DriverManager.getConnection (url,username,password);
// 建立帶問號的參數化語句
String template = " UPDATE music SET price=? WHERE id=? ";
PreparedStatement statement = connection.prepareStatement (template);
float newPrices[] = getNewPrices();
int recordingIDs = getIDs();
for(int i=0; i<recordingIDs.length;i++){
// 用setXxx代替?
statement.setFloat(1,newPrices[i]);
statement.setInt(2,recordingIDs[i]);
// 執行預備語句
statement.execute();}
2、使用PreparedStatement的好處:
1.依賴於服務器對預編譯查詢的支持,以及驅動程序處理原始查詢的效率,預備語句在性能上的優點可能有很大的不一樣。
2.安全是預備語句的另一個特色,咱們推薦在經過HTML表單接受用戶輸入,而後對數據庫進行更新時,必定要使用預備語句或存儲過程。
3.預備語句還可以正確地處理嵌入在字符串中的引號以及處理非字符數據(好比向數據庫發送序列化後的對象)
JDBC-CallableStatement(可調用語句)
1、使用CallableStatement(可調用語句)的優缺點:
1.優勢:語法錯誤能夠在編譯時找出來,而非在運行期間;數據庫存儲過程的運行可能比常規的
SQL查詢快得多;程序員只需知道輸入和輸出參數,不需瞭解表的結構。另外,因爲數據庫語言可以訪問數據庫本地的一下兒功能(序列,觸發器,多重遊標),所以用它來編寫存儲過程可能要比使用Java編程語言要簡易一些。
2. 缺點:存儲過程的商業邏輯在數據庫服務器上運行,而非客戶機或Web服務器。而行業的發展趨勢是儘量多地將商業邏輯移出數據庫,將它們放在 JavaBean組件(或者在大型的系統中,EnterPrise JavaBean組件)中,在Web構架上採用這種方式的主要動機是:數據庫訪問和網 絡I/O經常是性能的瓶頸。
2、使用CallableStatement在JAVA中調用數據庫存儲過程:
1.定義對數據庫過程的調用
A.無參數過程:{ call procedure_name}
B. 僅有輸入參數的過程:{call procedure_name(?,?...)}
C.有一個輸出參數的過程:{? Call procedure_name}
D.既有輸入參數又有輸出參數的過程{?=call procedure_name(?,?...)}
在過程的4種形式中要注意過程可能返回多個輸出參數,而且參數的索引值從輸出參數開始。所以前面最後例子中,第一個輸入參數的索引值是2而不是1。
2.爲過程準備CallableStatement
String procedure = 「{ ? = call procedure_name(?,?) }」;
CallableStatement statement = connection.prepareCall(procedure);
3.提供輸入參數的值
在執行存儲過程以前,咱們須要調用與所要設置的項以及參數的類型相對應的setXxx,替換標記出來的輸入參數
Statement.setString(2,」name」);
4.註冊輸出參數的類型
咱們必須使用registerOutParameter註冊每一個輸出參數的JDBC類型
Statement.registerOutParameter(n,type);
5.執行這個存儲過程
Statement.execute();
6.訪問返回的輸出參數
能夠經過調用getXxx訪問每一個對應的輸出參數
例如:
Connection connection = DriverManager.getConnection(url,username,password);
String procedure = 「{ ? = call myProc(?,?)}」;
CallableStatement statement = connection.prepareCall(procedure);
statement.setString(2,×××);
statement.setFloat(3,×××);
statement.registerOutParameter(1,Types.INTEGER);
statement.execute();
int row = statement.getInt(1);
CallableStatement 中定義的全部方法都用於處理 OUT 參數或 INOUT 參數的輸出部分:註冊 OUT 參數的 JDBC 類型(通常 SQL 類型)、從這些參數中檢索結果,或者檢查所返回的值是否爲 JDBC NULL。
一、建立 CallableStatement 對象
CallableStatement 對象是用 Connection 方法 prepareCall 建立的。下例建立 CallableStatement 的實例,其中含有對已儲存過程 getTestData 調用。該過程有兩個變量,但不含結果參數:
java
CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
其中?佔位符爲IN、OUT仍是INOUT參數,取決於已儲存過程getTestData。
二、IN和OUT參數
將IN參數傳給 CallableStatement 對象是經過 setXXX 方法完成的。該方法繼承自 PreparedStatement。所傳入參數的類型決定了所用的setXXX方法(例如,用 setFloat 來傳入 float 值等)。
若是已儲存過程返回 OUT 參數,則在執行 CallableStatement 對象之前必須先註冊每一個 OUT 參數的 JDBC 類型(這是必需的,由於某些 DBMS 要求 JDBC 類型)。註冊 JDBC 類型是用 registerOutParameter 方法來完成的。語句執行完後,CallableStatement 的 getXXX 方法將取回參數值。正確的 getXXX 方法是爲各參數所註冊的 JDBC 類型所對應的 Java 類型。換言之, registerOutParameter 使用的是 JDBC 類型(所以它與數據庫返回的 JDBC 類型匹配),而 getXXX 將之轉換爲 Java 類型。
做爲示例,下述代碼先註冊 OUT 參數,執行由 cstmt 所調用的已儲存過程,而後檢索在 OUT 參數中返回的值。方法 getByte 從第一個 OUT 參數中取出一個 Java 字節,而 getBigDecimal 從第二個 OUT 參數中取出一個 BigDecimal 對象(小數點後面帶三位數):
mysql
CallableStatement cstmt = con.prepareCall("{call getTestData(?, ?)}");
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.registerOutParameter(2, java.sql.Types.DECIMAL, 3);
cstmt.executeQuery();
byte x = cstmt.getByte(1);
java.math.BigDecimal n = cstmt.getBigDecimal(2, 3);
CallableStatement 與 ResultSet 不一樣,它不提供用增量方式檢索大 OUT 值的特殊機制。
三、INOUT參數
既支持輸入又接受輸出的參數(INOUT 參數)除了調用 registerOutParameter 方法外,還要求調用適當的 setXXX 方法(該方法是從 PreparedStatement 繼承來的)。setXXX 方法將參數值設置爲輸入參數,而 registerOutParameter 方法將它的 JDBC 類型註冊爲輸出參數。setXXX 方法提供一個 Java 值,而驅動程序先把這個值轉換爲 JDBC 值,而後將它送到數據庫中。這種 IN 值的 JDBC 類型和提供給 registerOutParameter 方法的 JDBC 類型應該相同。而後,要檢索輸出值,就要用對應的 getXXX 方法。例如,Java 類型爲byte 的參數應該使用方法 setByte 來賦輸入值。應該給registerOutParameter 提供類型爲 TINYINT 的 JDBC 類型,同時應使用 getByte 來檢索輸出值。
下例假設有一個已儲存過程 reviseTotal,其惟一參數是 INOUT 參數。方法setByte 把此參數設爲 25,驅動程序將把它做爲 JDBC TINYINT 類型送到數據庫中。接着,registerOutParameter 將該參數註冊爲 JDBC TINYINT。執行完該已儲存過程後,將返回一個新的 JDBC TINYINT 值。方法 getByte 將把這個新值做爲 Java byte 類型檢索。
程序員
CallableStatement cstmt = con.prepareCall("{call reviseTotal(?)}");
cstmt.setByte(1, 25);
cstmt.registerOutParameter(1, java.sql.Types.TINYINT);
cstmt.executeUpdate();
byte x = cstmt.getByte(1);
四、先檢索結果,再檢索 OUT 參數
由 於某些 DBMS 的限制,爲了實現最大的可移植性,建議先檢索由執行CallableStatement 對象所產生的結果,而後再用 CallableStatement.getXXX 方法來檢索 OUT 參數。若是 CallableStatement 對象返回多個 ResultSet 對象(經過調用 execute 方法),在檢索 OUT 參數前應先檢索全部的結果。這種狀況下,爲確保對全部的結果都進行了訪問,必須對 Statement 方法 getResultSet、getUpdateCount 和getMoreResults 進行調用,直到再也不有結果爲止。
檢索完全部的結果後,就可用 CallableStatement.getXXX 方法來檢索 OUT 參數中的值。
五、檢索做爲OUT參數的NULL值
返回到 OUT 參數中的值可能會是JDBC NULL。當出現這種情形時,將對 JDBC NULL 值進行轉換以使 getXXX 方法所返回的值爲 null、0 或 false,這取決於getXXX 方法類型。對於 ResultSet 對象,要知道0或false是否源於JDBCNULL的惟一方法,是用方法wasNull進行檢測。若是 getXXX 方法讀取的最後一個值是 JDBC NULL,則該方法返回 true,不然返回 flase。
sql
JDBC-Transation(事務處理)
1、Transation(事務處理)的概念:
在 更新數據庫時,默認狀況下,更改是永久性寫入到數據庫。然而這種默認行爲能夠經過編寫程序來關閉。在自動交付關閉的狀況下,若是在更新時發生問題,則對數 據庫的每一個更改都可以取消(或者說回退到最初的值)。若是更新成功,那麼以後能夠將這些更改永久性提交給數據庫。這種方式也稱爲事務管理。
咱們須要確保,要麼全部的操做都發生,要麼全部的操做都不發生。這就是事務管理的原則。
2、在JAVA中使用Transation(事務管理)保證數據庫的完整性:
我 們使用try-catch-finally塊來正確地應對事務管理,首先,記錄自動提交的當前狀態。而後,在try塊中,調用 setAutoCommit(false)並執行一系列的查詢或更新。若是發生故障,則在catch塊中調用rollback;若是事務成功,則在try 塊的結尾調用commit。無論哪一種方式,都在finally塊中重置自動提交的狀態。例如:
Connection connection = DriverManager.getConnection(url,username,password);
boolean autoCommit = connection.getAutoCommit();
Statement statement;
try{
connection.setAutoCommit(false); // 關閉數據庫的自動提交
statement = connection.createStatement();
statement.execute(…);
statement.execute(..);
…
connection.commit(); // 若是全部語句執行成功則提交事務
}
catch(SQLException sqle){
connection.rollback(); // 若是有異常發生則回滾全部的事務
}
finally{
if(statement!=null){statement.close();}
connection.setAutoCommit(autoCommit); // 重置自動提交的狀態
}
上 面的代碼中,從DriverManager獲取鏈接的語句在try/catch塊以外。這樣除非成功獲取鏈接,不然不會調用rollback。若是把獲取 鏈接的語句放在try/catch快以內,一旦在鏈接成功後發生異常,因爲rollback的做用會把已經創建的鏈接斷開。可是 getConnection方法也會拋出SQLException異常這個異常要麼被外圍的方法從新拋出,要麼在單獨的try/catch塊內捕獲。數據庫
JDBC的經常使用API
1、Connection接口:
1.createStatement():建立數據庫鏈接
2.prepareStatement(String sql):建立預處理語句
3.prepareCall(String sql):建立可調用語句
4.getAutoCommit():獲取自動提交的模式
5.setAutoCommit():設置自動提交的模式
6.commit():提交所執行的SQL語句
7.rollback():回滾所執行的SQL語句
8.getMetaData():獲取一個DatabaseMetaData對象,該對象包含了有關數據庫的基本信息
9.close():關閉數據庫鏈接
10.isClose():判斷數據庫鏈接是否超時或被顯示關閉
2、Statement接口:
1.execute(String sql):執行SQL語句,若是返回值是結果集則爲true,不然爲false
2.executeQuery(String sql):執行SQL語句,返回值爲ResultSet
3.executeUpdate(String sql):執行SQL語句,返回值爲所影響的行數
4.addBatch(String sql):向當前Statement對象的命令列表中添加新的批處理SQL語句
5.clearBatch():清空當前Statement對象的命令列表
6.executeBatch():執行當前Statement對象的批處理語句,返回值爲每一個語句所影響的函數數組
7.getConnection():返回建立了該Statement對象的Connection對象
8.getQueryTimeout():獲取等待處理結果的時間
9.setQueryTimeout():設置等待處理結果的時間
3、ResultSet接口:
1.first()/beforeFirst():將遊標移動到ResultSet中第一條記錄(的前面)
2.last()/afterLast():將遊標移動到ResultSet中最後一條記錄(的後面)
3.absolute(int column):將遊標移動到相對於第一行的指定行,負數則爲相對於最後一條記錄
4.relative(int rows):將遊標移動到相對於當前行的第幾行,正爲向下,負爲向上
5.next():將遊標下移一行
6.previous():將遊標上移一行
7.insertRow():向當前ResultSet和數據庫中被插入行處插入一條記錄
8.deleteRow():將當前ResultSet中的當前行和數據庫中對應的記錄刪除
9.updateRow():用當前ResultSet中已更新的記錄更新數據庫中對應的記錄
10.cancelUpdate():取消當前對ResultSet和數據庫中所作的操做
11.findColumn(String columnName):返回當前ResultSet中與指定列名對應的索引
12.getRow():返回ResultSet中的當前行號
13.refreshRow():更新當前ResultSet中的全部記錄
14.getMetaData():返回描述ResultSet的ResultSetMetaData對象
15.isAfterLast(): 是否到告終尾
16.isBeforeFirst(): 是否到了開頭
17.isFirst():是否第一條記錄
18.isLast(): 是否最後一條記錄
19.wasNull():檢查列值是否爲NULL值,若是列的類型爲基本類型,且數據庫中的值爲0,那麼
這項檢查就很重要。因爲數據庫NULL也返回0,因此0值和數據庫的NULL不能區分。若是列的類型爲對象,能夠簡單地將返回值與null比較
20.close():關閉當前ResultSet
4、ResultSetMetaData接口:
1.getColumnCount():返回ResultSet中列的數目
2.getColumnName():返回列在數據庫中的名稱
3.getColumnType():返回列的SQL類型
4.isReadOnly():表示該數據項是否爲只讀值
5.isNullable():表示該列是否能夠存儲NULL
基於JDBC的數據庫鏈接池技術研究與應用
Java應用程序訪問數據庫的基本原理
在 Java語言中,JDBC(Java DataBase Connection)是應用程序與數據庫溝通的橋樑, 即Java語言經過JDBC技術訪問數 據庫。JDBC是一種「開放」的方案,它爲數據庫應用開發人員、數據庫前臺工具開發人員提供了一種標準的應用程序設計接口,使開發人員能夠用純Java語 言編寫完整的數據庫應用程序。JDBC提供兩種API,分別是面向開發人員的API和麪向底層的JDBC驅動程序API,底層主要經過直接的JDBC驅動 和JDBC-ODBC橋驅動實現與數據庫的鏈接。
通常來講,Java應用程序訪問數據庫的過程(如圖1所示)是:
①裝載數據庫驅動程序;
②經過JDBC創建數據庫鏈接;
③訪問數據庫,執行SQL語句;
④斷開數據庫鏈接。
JDBC 做爲一種數據庫訪問技術,具備簡單易用的優勢。但使用這種模式進行Web應用程序開發,存在不少問題:首先,每一次Web請求都要創建一次數據庫鏈接。建 立鏈接是一個費時的活動,每次都得花費0.05s~1s的時間,並且系統還要分配內存資源。這個時間對於一次或幾回數據庫操做,或許感受不出系統有多大的 開銷。但是對於如今的Web應用,尤爲是大型電子商務網站,同時有幾百人甚至幾千人在線是很正常的事。在這種狀況下,頻繁的進行數據庫鏈接操做勢必佔用很 多的系統資源,網站的響應速度一定降低,嚴重的甚至會形成服務器的崩潰。不是危言聳聽,這就是制約某些電子商務網站發展的技術瓶頸問題。其次,對於每一次 數據庫鏈接,使用完後都得斷開。不然,若是程序出現異常而未能關閉,將會致使數據庫系統中的內存泄漏,最終將不得不重啓數據庫。還有,這種開發不能控制被 建立的鏈接對象數,系統資源會被毫無顧及的分配出去,如鏈接過多,也可能致使內存泄漏,服務器崩潰。
數據庫鏈接池(connection pool)的工做原理
一、基本概念及原理
數 據庫鏈接池的基本思想就是爲數據庫鏈接創建一個「緩衝池」。預先在緩衝池中放入必定數量的鏈接,當須要創建數據庫鏈接時,只需從「緩衝池」中取出一個,使 用完畢以後再放回去。咱們能夠經過設定鏈接池最大鏈接數來防止系統無盡的與數據庫鏈接。更爲重要的是咱們能夠經過鏈接池的管理機制監視數據庫的鏈接的數 量、使用狀況,爲系統開發、測試及性能調整提供依據。
二、服務器自帶的鏈接池
JDBC的API中沒有提供鏈接池的方法。一些大型的WEB應用服務器如BEA的WebLogic和IBM的WebSphere等提供了鏈接池的機制,可是必須有其第三方的專用類方法支持鏈接池的用法。
鏈接池關鍵問題分析
一、併發問題
爲 了使鏈接管理服務具備最大的通用性,必須考慮多線程環境,即併發問題。這個問題相對比較好解決,由於Java語言自身提供了對併發管理的支持,使用 synchronized關鍵字便可確保線程是同步的。使用方法爲直接在類方法前面加上synchronized關鍵字,如:
public synchronized Connection getConnection()
二、多數據庫服務器和多用戶
對 於大型的企業級應用,經常須要同時鏈接不一樣的數據庫(如鏈接Oracle和Sybase)。如何鏈接不一樣的數據庫呢?咱們採用的策略是:設計一個符合單例 模式的鏈接池管理類,在鏈接池管理類的惟一實例被建立時讀取一個資源文件,其中資源文件中存放着多個數據庫的url地址 (<poolName.url>)、用戶名(<poolName.user>)、密碼 (<poolName.password>)等信息。如 tx.url=172.21.15.123:5000/tx_it,tx.user=yang,tx.password=yang321。根據資源文件提 供的信息,建立多個鏈接池類的實例,每個實例都是一個特定數據庫的鏈接池。鏈接池管理類實例爲每一個鏈接池實例取一個名字,經過不一樣的名字來管理不一樣的連 接池。
對於同一個數據庫有多個用戶使用不一樣的名稱和密碼訪問的狀況,也能夠經過資源文件處理,即在資源文件中設置多個具備相同url地址,但具備不一樣用戶名和密碼的數據庫鏈接信息。
三、事務處理
咱們知道,事務具備原子性,此時要求對數據庫的操做符合「ALL-ALL-NOTHING」原則,即對於一組SQL語句要麼全作,要麼全不作。
在 Java語言中,Connection類自己提供了對事務的支持,能夠經過設置Connection的AutoCommit屬性爲false,而後顯式的 調用commit或rollback方法來實現。但要高效的進行Connection複用,就必須提供相應的事務支持機制。可採用每個事務獨佔一個鏈接 來實現,這種方法能夠大大下降事務管理的複雜性。
四、鏈接池的分配與釋放
鏈接池的分配與釋放,對系統的性能有很大的影響。合理的分配與釋放,能夠提升鏈接的複用度,從而下降創建新鏈接的開銷,同時還能夠加快用戶的訪問速度。
對 於鏈接的管理可以使用空閒池。即把已經建立但還沒有分配出去的鏈接按建立時間存放到一個空閒池中。每當用戶請求一個鏈接時,系統首先檢查空閒池內有沒有空閒連 接。若是有就把創建時間最長(經過容器的順序存放實現)的那個鏈接分配給他(實際是先作鏈接是否有效的判斷,若是可用就分配給用戶,如不可用就把這個鏈接 從空閒池刪掉,從新檢測空閒池是否還有鏈接);若是沒有則檢查當前所開鏈接池是否達到鏈接池所容許的最大鏈接數(maxConn),若是沒有達到,就新建 一個鏈接,若是已經達到,就等待必定的時間(timeout)。若是在等待的時間內有鏈接被釋放出來就能夠把這個鏈接分配給等待的用戶,若是等待時間超過 預約時間timeout,則返回空值(null)。系統對已經分配出去正在使用的鏈接只作計數,當使用完後再返還給空閒池。對於空閒鏈接的狀態,可開闢專 門的線程定時檢測,這樣會花費必定的系統開銷,但能夠保證較快的響應速度。也可採起不開闢專門線程,只是在分配前檢測的方法。
五、鏈接池的配置與維護
連 接池中到底應該放置多少鏈接,才能使系統的性能最佳?系統可採起設置最小鏈接數(minConn)和最大鏈接數(maxConn)來控制鏈接池中的鏈接。 最小鏈接數是系統啓動時鏈接池所建立的鏈接數。若是建立過多,則系統啓動就慢,但建立後系統的響應速度會很快;若是建立過少,則系統啓動的很快,響應起來 卻慢。
能夠在開發時,設置較小的最小鏈接數,開發起來會快,而在系統實際使用時設置較大的,由於這樣對訪問客戶來講速度會快些。最大鏈接數是鏈接池中容許鏈接的最大數目,具體設置多少,要看系統的訪問量,可經過反覆測試,找到最佳點。
如何確保鏈接池中的最小鏈接數呢?有動態和靜態兩種策略。動態即每隔必定時間就對鏈接池進行檢測,若是發現鏈接數量小於最小鏈接數,則補充相應數量的新鏈接,以保證鏈接池的正常運轉。靜態是發現空閒鏈接不夠時再去檢查。
鏈接池的實現
一、鏈接池模型
連 接池類是對某一數據庫全部鏈接的「緩衝池」,主要實現如下功能:①從鏈接池獲取或建立可用鏈接;②使用完畢以後,把鏈接返還給鏈接池;③在系統關閉前,斷 開全部鏈接並釋放鏈接佔用的系統資源;④還可以處理無效鏈接(原來登記爲可用的鏈接,因爲某種緣由再也不可用,如超時,通信問題),並可以限制鏈接池中的連 接總數不低於某個預約值和不超過某個預約值。
鏈接池管理類是鏈接池類的外覆類(wrapper),符合單例模式,即系統中只能有一個連 接池管理類的實例。其主要用於對多個鏈接池對象的管理,具備如下功能:①裝載並註冊特定數據庫的JDBC驅動程序;②根據屬性文件給定的信息,建立鏈接池 對象;③爲方便管理多個鏈接池對象,爲每個鏈接池對象取一個名字,實現鏈接池名字與其實例之間的映射;④跟蹤客戶使用鏈接狀況,以便須要是關閉鏈接釋放 資源。鏈接池管理類的引入主要是爲了方便對多個鏈接池的使用和管理,如系統須要鏈接不一樣的數據庫,或鏈接相同的數據庫但因爲安全性問題,須要不一樣的用戶使 用不一樣的名稱和密碼。
二、鏈接池實現
下面給出鏈接池類和鏈接池管理類的主要屬性及所要實現的基本接口:
public class DBConnectionPool implements TimerListener{
private int checkedOut;//已被分配出去的鏈接數
private ArrayList freeConnections = new ArrayList();//容器,空閒池,根據建立時間順序存放已建立
還沒有分配出去的鏈接
private int minConn;//鏈接池裏鏈接的最小數量
private int maxConn;//鏈接池裏容許存在的最大鏈接數
private String name;//爲這個鏈接池取個名字,方便管理
private String password;//鏈接數據庫時須要的密碼
private String url;//所要建立鏈接的數據庫的地址
private String user;//鏈接數據庫時須要的用戶名
public Timer timer;//定時器
public DBConnectionPool(String name, String URL, String user, Stringpassword, int maxConn)
public synchronized void freeConnection(Connection con) //使用完畢以後把鏈接返還給空閒池
public synchronized Connection getConnection(long timeout)//獲得一個鏈接,timeout是等待時間
public synchronized void release()//斷開全部鏈接,釋放佔用的系統資源
private Connection newConnection()//新建一個數據庫鏈接
public synchronized void TimerEvent() //定時器事件處理函數
}
public class DBConnectionManager {
static private DBConnectionManager instance;//鏈接池管理類的惟一實例
static private int clients;//客戶數量
private ArrayList drivers = new ArrayList();//容器,存放數據庫驅動程序
private HashMap pools = new HashMap ();//以name/value的形式存取鏈接池對象的名字及鏈接池對象
private void loadDrivers(Properties props)//裝載數據庫驅動程序
private void createPools(Properties props)//根據屬性文件提供的信息,建立一個或多個鏈接池
private DBConnectionManager()//私有構造函數,在其中調用初始化函數init()
private void init()//初始化鏈接池管理類的惟一實例,由私有構造函數調用
static synchronized public DBConnectionManager getInstance()//若是惟一的實例instance已經建立,直接返回這個實例;不然,調用私有構造函數,建立鏈接池管理類的惟一實例
public Connection getConnection(String name)//從名字爲name的鏈接池對象//中獲得一個鏈接
public Connection getConnection(String name, long time)//從名字爲name 的鏈接池對象中取得一個鏈接,time是等待時間
public void freeConnection(String name, Connection con)//釋放一個鏈接name是一個鏈接池對象的名
public synchronized void release()//釋放全部資源
}
三、鏈接池使用
上面所實現的鏈接池在程序開發時如何應用到系統中呢?下面以Servlet爲例說明鏈接池的使用。
Servlet 的生命週期是:在開始創建servlet時,調用其初始化(init)方法。以後每一個用戶請求都致使一個調用前面創建的實例的service方法的線程。 最後,當服務器決定卸載一個servlet時,它首先調用該servlet的 destroy方法。 根據servlet的特色,咱們能夠在初始化函數中 生成鏈接池管理類的惟一實例(其中包括建立一個或多個鏈接池)。如:
public void init() throws ServletException
{
// getInstance()?DBConnectionManager()?init()
connMgr = DBConnectionManager.getInstance();
}
而後就能夠在service方法中經過鏈接池名稱使用鏈接池,執行數據庫操做。最後在destroy方法中釋放佔用的系統資源,如:
public void destroy() {
connMgr.release();
super.destroy();
}
編程4 一種簡單JDBC鏈接池的實現數組
JDBC鏈接池
在標準JDBC對應用的接口中,並無提供資源的管理方法。因此,缺省的資源管理由應用本身負責。雖然在 JDBC規範中,屢次說起資源的關閉/回收及其餘的合理運用。但最穩妥的方式,仍是爲應用提供有效的管理手段。因此,JDBC爲第三方應用服務器 (Application Server)提供了一個由數據庫廠家實現的管理標準接口:鏈接緩衝(connection pooling)。引入了鏈接池 ( Connection Pool )的概念 ,也就是以緩衝池的機制管理數據庫的資源。
JDBC最經常使用的資源有三類:
-Connection: 數據庫鏈接。
-Statement: 會話聲明。
-ResultSet: 結果集遊標。
分別存在如下的關係 :
安全
這是一種 ‘爺-父-子’ 的關係,對Connection的管理,就是對數據庫資源的管理。舉個例子: 若是想肯定某個數據庫鏈接 (Connection)是否超時,則須要肯定其(全部的)子Statement是否超時,一樣,須要肯定全部相關的ResultSet是否超時;在關閉 Connection前,須要關閉全部相關的Statement和ResultSet。
所以,鏈接池(Connection Pool)所起到的做用,不只僅簡單地管理Connection,還涉及到 Statement和ResultSet。
2.3鏈接池(ConnectionPool)與資源管理
ConnectionPool以緩衝池的機制,在必定數量上限範圍內,控制管理Connection,Statement和ResultSet。任何數據庫的資源是有限的,若是被耗盡,則沒法得到更多的數據服務。
在大多數狀況下,資源的耗盡不是因爲應用的正常負載太高,而是程序緣由。
在 實際工做中,數據資源每每是瓶頸資源,不一樣的應用都會訪問同一數據源。其中某個應用耗盡了數據庫資源後,意味其餘的應用也沒法正常運行。因 此,ConnectionPool的第一個任務是限制:每一個應用或系統能夠擁有的最大資源。也就是肯定鏈接池的大小(PoolSize)。
ConnectionPool 的第二個任務:在鏈接池的大小(PoolSize)範圍內,最大限度地使用資源,縮短數據庫訪問的使用週期。許多數據庫中,鏈接(Connection) 並非資源的最小單元,控制Statement資源比Connection更重要。以Oracle爲例:
每申請一個鏈接 (Connection)會在物理網絡(如 TCP/IP網絡)上創建一個用於通信的鏈接,在此鏈接上還能夠申請必定數量的Statement。同一鏈接 可提供的活躍Statement數量能夠達到幾百。 在節約網絡資源的同時,縮短了每次會話週期(物理鏈接的創建是個費時的操做)。但在通常的應用中,多 數按照2.1範例操做,這樣有10個程序調用,則會產生10次物理鏈接,每一個Statement單獨佔用一個物理鏈接,這是極大的資源浪 費。 ConnectionPool能夠解決這個問題,讓幾10、幾百個Statement只佔用同一個物理鏈接, 發揮數據庫原有的優勢。
經過ConnectionPool對資源的有效管理,應用能夠得到的Statement總數到達 :
(併發物理鏈接數) x (每一個鏈接可提供的Statement數量)
例 如某種數據庫可同時創建的物理鏈接數爲 200個,每一個鏈接可同時提供250個Statement,那麼ConnectionPool最終爲應用提供的並 發Statement總數爲: 200 x 250 = 50,000個。這是個併發數字,不多有系統會突破這個量級。因此在本節的開始,指出資源的耗盡 與應用程序直接管理有關。
對資源的優化管理,很大程度上依靠數據庫自身的JDBC Driver是否具有。有些數據庫的JDBC Driver並不支持Connection與Statement之間的邏輯鏈接功能,如SQLServer,咱們只能等待她自身的更新版本了。
對資源的申請、釋放、回收、共享和同步,這些管理是複雜精密的。因此,ConnectionPool另外一個功能就是,封裝這些操做,爲應用提供簡單的,甚至是不改變應用風格的調用接口。 服務器3.簡單JDBC鏈接池的實現
根據第二章中原理機制,Snap-ConnectionPool(一種簡單快速的鏈接池工具)按照部分的JDBC規範,實現了鏈接池所具有的對數據庫資源有效管理功能。
3.1體系描述
在 JDBC規範中,應用經過驅動接口(Driver Interface)直接方法數據庫的資源。爲了有效、合理地管理資源,在應用與 JDBC Driver之間,增長了鏈接池: Snap-ConnectionPool。而且經過面向對象的機制,使鏈接池的大部分操做是透明的。參見下 圖,Snap-ConnectionPool的體系:
網絡
圖中所示,經過實現JDBC的部分資源對象接口( Connection, Statement, ResultSet ), 在 Snap-ConnectionPool內部分別產生三種邏輯資源對象: PooledConnection, PooledStatement 和 PooledResultSet。它們也是鏈接池主要的管理操做對象,而且繼承了JDBC中相應的從屬關係。這樣的體系有如下幾個特色:
-透明性。在不改變應用原有的使用JDBC驅動接口的前提下,提供資源管理的服務。應用系統,如同原有的 JDBC,使用鏈接池提供的邏輯對象資源。簡化了應用程序的鏈接池改造。
-資源封裝。複雜的資源管理被封裝在 Snap-ConnectionPool內部,不須要應用系統過多的干涉。管理操做的可靠性、安全性由鏈接池保證。應用的干涉(如:主動關閉資源),只起到優化系統性能的做用,遺漏操做不會帶來負面影響。
- 資源合理應用。按照JDBC中資源的從屬關係,Snap-ConnectionPool不只對Connection進行緩衝處理,對Statement也 有相應的機制處理。在2.3已描述,合理運用Connection和Statement之間的關係,能夠更大限度地使用資源。因此,Snap- ConnectionPool封裝了Connection資源,經過內部管理PooledConnection,爲應用系統提供更多的Statement 資源。
-資源連鎖管理。Snap-ConnectionPool包含的三種邏輯對象,繼承了JDBC中相應對象之間的從屬關係。在內部 管理中,也依照從屬關係進行連鎖管理。例如:判斷一個Connection是否超時,須要根據所包含的Statement是否活躍;判斷 Statement也要根據ResultSet的活躍程度。3.2鏈接池集中管理ConnectionManager
ConnectionPool是Snap- ConnectionPool的鏈接池對象。在Snap-ConnectionPool內部,能夠指定多個不一樣的鏈接池(ConnectionPool) 爲應用服務。ConnectionManager管理全部的鏈接池,每一個鏈接池以不一樣的名稱區別。經過配置文件適應不一樣的數據庫種類。以下圖所示:
經過ConnectionManager,能夠同時管理多個不一樣的鏈接池,提供通一的管理界面。在應用系統中經過 ConnectionManager和相關的配置文件,能夠將凌亂散落在各自應用程序中的數據庫配置信息(包括:數據庫名、用戶、密碼等信息),集中在一 個文件中。便於系統的維護工做。
3.3鏈接池使用範例
對2.1的標準JDBC的使用範例,改成使用鏈接池,結果以下:
import java.sql.*;
import net.snapbug.util.dbtool.*;
…
..ConnectionPool dbConn = ConnectionManager
.getConnectionPool("testOracle" );
Statement st = dbConn.createStatement();
ResultSet rs = st.executeQuery(
「select * from demo_table」 );
…
some data source operation
in herers.close();st.close();
在 例子中,Snap-ConnectionPool封裝了應用對Connection的管理。只要改變JDBC獲取Connection的方法,爲獲取鏈接 池(ConnectionPool)(粗體部分),其餘的數據操做均可以不作修改。按照這樣的方式,Snap-ConnectionPool可幫助應用有 效地管理數據庫資源。若是應用忽視了最後資源的釋放: rs.close() 和 st.close(),鏈接池會經過超時(time-out)機制,自 動回收。
4.小結
不管是Snap-ConnectionPool仍是其餘的數據庫鏈接池,都應當具有一下基本功能:
-對源數據庫資源的保護
-充分利用發揮數據庫的有效資源
-簡化應用的數據庫接口,封閉資源管理。
-對應用遺留資源的自動回收和整理,提升資源的再次利用率。
在這個前提下,應用程序才能投入更多的精力於各自的業務邏輯中。數據庫資源也再也不成爲系統的瓶頸。
注: 在網站 www.snapbug.net可免費下載Snap-ConnectionPool及更詳細的文檔。
附:JDBC鏈接各類數據庫的方法及技巧
1、鏈接各類數據庫方式速查表
下面羅列了各類數據庫使用JDBC鏈接的方式,能夠做爲一個手冊使用。
一、Oracle8/8i/9i數據庫(thin模式)
Class.forName(「oracle.jdbc.driver.OracleDriver「).newInstance();
String url=「jdbc:oracle:thin:@localhost :1521:orcl「; //orcl爲數據庫的SIDString user=「test「;String password=「test「;Connection conn= DriverManager.getConnection(url,user,password); 二、DB2數據庫Class.forName(「com.ibm.db2.jdbc.app.DB2Driver 「).newInstance();String url=「jdbc:db2://localhost:5000/sample「; //sample爲你的數據庫名String user=「admin「;String password=「「;Connection conn= DriverManager.getConnection(url,user,password); 三、Sql Server7.0/2000數據庫Class.forName(「com.microsoft.jdbc.sqlserver.SQLServerDriver「).newInstance();String url=「jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb「;//mydb爲數據庫String user=「sa「;String password=「「;Connection conn= DriverManager.getConnection(url,user,password); 四、Sybase數據庫Class.forName(「com.sybase.jdbc.SybDriver「).newInstance();String url =「 jdbc:sybase:Tds:localhost:5007/myDB「;//myDB爲你的數據庫名Properties sysProps = System.getProperties();SysProps.put(「user「,「userid「);SysProps.put(「password「,「user_password「);Connection conn= DriverManager.getConnection(url, SysProps); 五、Informix數據庫Class.forName(「com.informix.jdbc.IfxDriver「).newInstance();String url = 「jdbc:informix-sqli://123.45.67.89:1533/myDB:INFORMIXSERVER=myserver;user=testuser;password=testpassword「; //myDB爲數據庫名Connection conn= DriverManager.getConnection(url); 六、MySQL數據庫Class.forName(「org.gjt.mm.mysql.Driver「).newInstance();String url =「jdbc:mysql://localhost/myDB?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1「//myDB爲數據庫名Connection conn= DriverManager.getConnection(url); 七、PostgreSQL數據庫Class.forName(「org.postgresql.Driver「).newInstance();String url =「jdbc:postgresql://localhost/myDB「 //myDB爲數據庫名String user=「myuser「;String password=「mypassword「;Connection conn= DriverManager.getConnection(url,user,password); 八、access數據庫直連用ODBC的Class.forName(「sun.jdbc.odbc.JdbcOdbcDriver「) ;String url=「jdbc:odbc:Driver={MicroSoft Access Driver (*.mdb)};DBQ=「+application.getRealPath(「/Data/ReportDemo.mdb「);Connection conn = DriverManager.getConnection(url,「「,「」);Statement stmtNew=conn.createStatement() ; 2、JDBC鏈接MySql方式 下面是使用JDBC鏈接MySql的一個小的教程 一、查找驅動程序 MySQL目前提供的java驅動程序爲Connection/J,能夠從MySQL官方網站下載,並找到mysql-connector-java-3.0.15-ga-bin.jar文件,此驅動程序爲純java驅動程序,不需作其餘配置。二、動態指定classpath 若是須要執行時動態指定classpath,就在執行時採用-cp方式。不然將上面的.jar文件加入到classpath環境變量中。 三、加載驅動程序try{ Class.forName(com.mysql.jdbc.Driver); System.out.println(Success loading Mysql Driver!);}catch(Exception e){ System.out.println(Error loading Mysql Driver!); e.printStackTrace();} 四、設置鏈接的urljdbc:mysql://localhost/databasename[?pa=va][&pa=va] 3、如下列出了在使用JDBC來鏈接Oracle數據庫時可使用的一些技巧 一、在客戶端軟件開發中使用Thin驅動程序 在開發Java軟件方面,Oracle的數據庫提供了四種類型的驅動程序,二種用於應用軟件、applets、servlets等客戶端軟件,另外二種 用於數據庫中的Java存儲過程等服務器端軟件。在客戶機端軟件的開發中,咱們能夠選擇OCI驅動程序或Thin驅動程序。OCI驅動程序利用Java本 地化接口(JNI),經過Oracle客戶端軟件與數據庫進行通信。Thin驅動程序是純Java驅動程序,它直接與數據庫進行通信。爲了得到最高的性 能,Oracle建議在客戶端軟件的開發中使用OCI驅動程序,這彷佛是正確的。但我建議使用Thin驅動程序,由於經過屢次測試發現,在一般狀況 下,Thin驅動程序的性能都超過了OCI驅動程序。 二、關閉自動提交功能,提升系統性能 在第一次創建與數據庫的鏈接時,在缺省狀況下,鏈接是在自動提交模式下的。爲了得到更好的性能,能夠經過調用帶布爾值false參數的Connection類的setAutoCommit()方法關閉自動提交功能,以下所示: conn.setAutoCommit(false); 值得注意的是,一旦關閉了自動提交功能,咱們就須要經過調用Connection類的commit()和rollback()方法來人工的方式對事務進行管理。三、在動態SQL或有時間限制的命令中使用Statement對象 在執行SQL命令時,咱們有二種選擇:可使用PreparedStatement對象,也可使用Statement對象。不管多少次地使用同一個 SQL命令,PreparedStatement都只對它解析和編譯一次。當使用Statement對象時,每次執行一個SQL命令時,都會對它進行解析 和編譯。這可能會使你認爲,使用PreparedStatement對象比使用Statement對象的速度更快。然而,我進行的測試代表,在客戶端軟件 中,狀況並不是如此。所以,在有時間限制的SQL操做中,除非成批地處理SQL命令,咱們應當考慮使用Statement對象。 此外,使用Statement對象也使得編寫動態SQL命令更加簡單,由於咱們能夠將字符串鏈接在一塊兒,創建一個有效的SQL命令。所以,我認爲,Statement對象可使動態SQL命令的建立和執行變得更加簡單。 四、利用helper函數對動態SQL命令進行格式化 在建立使用Statement對象執行的動態SQL命令時,咱們須要處理一些格式化方面的問題。例如,若是咱們想建立一個將名字O'Reilly插入表 中的SQL命令,則必須使用二個相連的「''」號替換O'Reilly中的「'」號。完成這些工做的最好的方法是建立一個完成替換操做的helper方 法,而後在鏈接字符串心服用公式表達一個SQL命令時,使用建立的helper方法。與此相似的是,咱們可讓helper方法接受一個Date型的值, 而後讓它輸出基於Oracle的to_date()函數的字符串表達式。 五、利用PreparedStatement對象提升數據庫的整體效率 在使用PreparedStatement對象執行SQL命令時,命令被數據庫進行解析和編譯,而後被放到命令緩衝區。而後,每當執行同一個 PreparedStatement對象時,它就會被再解析一次,但不會被再次編譯。在緩衝區中能夠發現預編譯的命令,而且能夠從新使用。在有大量用戶的 企業級應用軟件中,常常會重複執行相同的SQL命令,使用PreparedStatement對象帶來的編譯次數的減小可以提升數據庫的整體性能。若是不 是在客戶端建立、預備、執行PreparedStatement任務須要的時間長於Statement任務,我會建議在除動態SQL命令以外的全部狀況下 使用PreparedStatement對象。 六、在成批處理重複的插入或更新操做中使用PreparedStatement對象 若是成批地處理插入和更新操做,就可以顯著地減小它們所須要的時間。Oracle提供的Statement和 CallableStatement並不真正地支持批處理,只有PreparedStatement對象才真正地支持批處理。咱們可使用 addBatch()和executeBatch()方法選擇標準的JDBC批處理,或者經過利用PreparedStatement對象的 setExecuteBatch()方法和標準的executeUpdate()方法選擇速度更快的Oracle專有的方法。要使用Oracle專有的批 處理機制,能夠以以下所示的方式調用setExecuteBatch():PreparedStatement pstmt3D null;try { ((OraclePreparedStatement)pstmt).setExecuteBatch(30); ... pstmt.executeUpdate();} 調用setExecuteBatch()時指定的值是一個上限,當達到該值時,就會自動地引起SQL命令執行,標準的executeUpdate()方 法就會被做爲批處理送到數據庫中。咱們能夠經過調用PreparedStatement類的sendBatch()方法隨時傳輸批處理任務。七、使用Oracle locator方法插入、更新大對象(LOB) Oracle的PreparedStatement類不徹底支持BLOB和CLOB等大對象的處理,尤爲是Thin驅動程序不支持利用 PreparedStatement對象的setObject()和setBinaryStream()方法設置BLOB的值,也不支持利用 setCharacterStream()方法設置CLOB的值。只有locator自己中的方法纔可以從數據庫中獲取LOB類型的值。可使用 PreparedStatement對象插入或更新LOB,但須要使用locator才能獲取LOB的值。因爲存在這二個問題,所以,我建議使用 locator的方法來插入、更新或獲取LOB的值。 八、使用SQL92語法調用存儲過程 在調用存儲過程時,咱們可使用SQL92或Oracle PL/SQL,因爲使用Oracle PL/SQL並無什麼實際的好處,並且會給之後維護你的應用程序的開發人員帶來麻煩,所以,我建議在調用存儲過程時使用SQL92。 九、使用Object SQL將對象模式轉移到數據庫中 既然能夠將Oracle的數據庫做爲一種面向對象的數據庫來使用,就能夠考慮將應用程序中的面向對象模式轉到數據庫中。目前的方法是建立Java bean做爲假裝的數據庫對象,將它們的屬性映射到關係表中,而後在這些bean中添加方法。儘管這樣做在Java中沒有什麼問題,但因爲操做都是在數據 庫以外進行的,所以其餘訪問數據庫的應用軟件沒法利用對象模式。若是利用Oracle的面嚮對象的技術,能夠經過建立一個新的數據庫對象類型在數據庫中模 仿其數據和操做,而後使用JPublisher等工具生成本身的Java bean類。若是使用這種方式,不但Java應用程序可使用應用軟件的對象模式,其餘須要共享你的應用中的數據和操做的應用軟件也可使用應用軟件中的 對象模式。 十、利用SQL完成數據庫內的操做 我要向你們介紹的最重要的經驗是充分利用SQL的面向集合的方法來解決數據庫處理需求,而不是使用Java等過程化的編程語言。 若是編程人員要在一個表中查找許多行,結果中的每一個行都會查找其餘表中的數據,最後,編程人員建立了獨立的UPDATE命令來成批地更新第一個表中的數 據。與此相似的任務能夠經過在set子句中使用多列子查詢而在一個UPDATE命令中完成。當可以在單一的SQL命令中完成任務,何須要讓數據在網上流來 流去的?我建議用戶認真學習如何最大限度地發揮SQL的功能.