JDBC概述
在Java中,數據庫存取技術可分為如下幾類:JDBC直接訪問數據庫、JDO技術、第三方O/R工具,如Hibernate, ibatis 等。
JDBC是java訪問數據庫的基石,JDO, Hibernate等只是更好的封裝了JDBC。
JDBC(Java Database Connectivity)是一個獨立于特定數據庫管理系統、通用的SQL數據庫存取和操作的公共接口(一組API),定義了用來訪問數據庫的標準Java類庫,使用這個類庫可以以一種標準的方法、方便地訪問數據庫資源。
JDBC為訪問不同的數據庫提供了一種統一的途徑,為開發者屏蔽了一些細節問題。
JDBC的目標是使Java程序員使用JDBC可以連接任何提供了JDBC驅動程序的數據庫系統,這樣就使得程序員無需對特定的數據庫系統的特點有過多的了解,從而大大簡化和加快了開發過程。
JDBC接口(API)包括兩個層次:
- 面向應用的API:Java API,抽象接口,供應用程序開發人員使用(連接數據庫,執行SQL語句,獲得結果)。
- 面向數據庫的API:Java Driver API,供開發商開發數據庫驅動程序用。
加載與注冊 JDBC 驅動
Java.sql.Driver 接口是所有JDBC驅動程序需要實現的接口。這個接口是提供給數據庫廠商使用的,不同數據庫廠商提供不同的實現。
在程序中不需要直接去訪問實現了 Driver 接口的類,而是由驅動程序管理器類(java.sql.DriverManager)去調用這些Driver實現。
加載 JDBC 驅動需調用 Class 類的靜態方法 forName(),需要向其傳遞要加載的 JDBC 驅動的類名,并提供相關驅動。例如:加入 mysql 驅動:
- 解壓 mysql-connector-java-5.1.7.zip
- 在當前項目下新建 lib 目錄
- 把 mysql-connector-java-5.1.7-bin.jar 復制到 lib 目錄下
- 右鍵 build-path , add to buildpath 加入到類路徑下.
DriverManager 類是驅動程序管理器類,負責管理驅動程序通常不用顯式調用 DriverManager 類的 registerDriver() 方法來注冊驅動程序類的實例,因為 Driver 接口的驅動程序類都包含了靜態代碼塊,在這個靜態代碼塊中,會調用 DriverManager.registerDriver() 方法來注冊自身的一個實例。
建立連接
可以調用 DriverManager 類的 getConnection() 方法建立到數據庫的連接。
JDBC URL 用于標識一個被注冊的驅動程序,驅動程序管理器通過這個 URL 選擇正確的驅動程序,從而建立到數據庫的連接。
JDBC URL的標準由三部分組成,各部分間用冒號分隔。
jdbc:<子協議>:<子名稱>
協議:JDBC URL中的協議總是jdbc
子協議:子協議用于標識一個數據庫驅動程序
子名稱:一種標識數據庫的方法。子名稱可以依不同的子協議而變化,用子名稱的目的是為了定位數據庫提供足夠的信息
- 幾種常用數據庫的JDBC URL
- 對于 Oracle 數據庫連接,采用如下形式:
jdbc:oracle:thin:@localhost:1521:sid
- 對于 SQLServer 數據庫連接,采用如下形式:
jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=sid
- 對于 MYSQL 數據庫連接,采用如下形式:
jdbc:mysql://localhost:3306/sid
- 對于 Oracle 數據庫連接,采用如下形式:
//oracle數據庫驅動的完整類名,mysql的為com.mysql.jdbc.Driver
String driver = "oracle.jdbc.driver.OracleDriver";
// 通過反射得到驅動對象,加載數據庫驅動程序(對應的 Driver 實現類中有注冊驅動的靜態代碼塊.)
Class.forName(driver);
// 通過 DriverManager 的 getConnection() 方法獲取數據庫連接.
Connection connection = DriverManager.getConnection(jdbcUrl, user, password);
訪問數據庫
數據庫連接被用于向數據庫服務器發送命令和 SQL 語句,在連接建立后,需要對數據庫進行訪問,執行 sql 語句。
在 java.sql 包中有 3 個接口分別定義了對數據庫的調用的不同方式:Statement、PrepatedStatement、CallableStatement。
Statement
通過調用 Connection 對象的 createStatement 方法創建該對象,該對象用于執行靜態的 SQL 語句,并且返回執行結果,Statement 接口中定義了下列方法用于執行 SQL 語句:
ResultSet excuteQuery(String sql)
傳入的sql語句應為SELECT,ResultSet
是結果集. 封裝了使用 JDBC 進行查詢的結果.
int excuteUpdate(String sql)
傳入的sql語句可以為INSRET、UPDATE 或 DELETE這些不返回結果的語句,也可以是一些sql DDL語句。返回值是一個整數,指示受影響的行數(即更新計數)。對于 CREATE TABLE 或 DROP TABLE等不操作行的語句,executeUpdate 的返回值總為零。
ResultSet
通過調用 Statement 對象的 excuteQuery() 方法創建該對象,ResultSet 對象以邏輯表格的形式封裝了執行數據庫操作的結果集 ,ResultSet 接口由數據庫廠商實現,ResultSet 對象維護了一個指向當前數據行的游標,初始的時候,游標在第一行之前, 可以通過 ResultSet 對象的 next() 方法移動到下一行。當指針對位到一行時, 可以通過調用 getXxx(index) 或 getXxx(columnName)獲取每一列的值. 例如: getInt(1), getString("name")。
關閉資源
Connection、Statement、resultSet 都是應用程序和數據庫服務器的連接資源. 使用后一定要關閉.
需要在 finally 中關閉 Connection 和 Statement 、resultSet對象,關閉的順序是: 先關閉后獲取的. 即先關閉 resultSet、Statement 后關閉 Connection。
Statement statement = connection.createStatement();
String sql = "SELECT id, name, email, birth FROM customers";
// 執行查詢, 得到 ResultSet
ResultSet rs = statement.executeQuery(sql);
// 處理 ResultSet
while(rs.next()){
int id = rs.getInt(1);
String name = rs.getString("name");
String email = rs.getString(3);
Date birth = rs.getDate(4);
System.out.println(id);
System.out.println(name);
System.out.println(email);
System.out.println(birth);
}
//finally{...}最后釋放資源
java類型 | SQL類型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR,LONGVARCHAR |
byte array | BINARY , VAR BINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
JAVA數據類型和MYSQL數據類型對應關系表 |
SQL注入攻擊
SQL注入是利用某些系統沒有對用戶輸入的數據進行充分的檢查,而在用戶輸入數據中注入非法的 SQL語句段或命令從而利用系統的 SQL引擎完成惡意行為的做法。對于Java而言,要防范 SQL 注入,只要用 PreparedStatement 取代 Statement 就可以了。
String username = "a' or password = ";
String password = "or '1'='1";
String sql = "select * from users where username ='"+username+"'and password ='"+password+"'";
sql = "select * from users where username = 'a' or password ='and password =' or '1'='1'";
PreparedStatement
可以通過調用 Connection 對象的 preparedStatement() 方法獲取 PreparedStatement 對象。
PreparedStatement 接口是 Statement 的子接口,它表示一條預編譯過的 SQL 語句。
PreparedStatement 對象所代表的 SQL 語句中的參數用問號(?)來表示,調用 PreparedStatement 對象的 setXXX() 方法來設置這些參數. setXXX() 方法有兩個參數,第一個參數是要設置的 SQL 語句中的參數的索引(從 1 開始),第二個是設置的 SQL 語句中的參數的值。
執行sql語句:excuteUpdate() 和 excuteQuery(),執行時不再需要傳入sql語句。
String sql = "insert into table values(?,?)";
PreparedStatement preparedStatement = connection.preparedStatement(sql);
preparedStatement.setInt(1,5);
preparedStatement.setString(2,"Hello");
PreparedStatement和Statement相比:
- 代碼的可讀性和可維護性更高。
- PreparedStatement 能最大可能提高性能:
- DBServer會對預編譯語句提供性能優化。因為預編譯語句有可能被重復調用,所以語句在被DBServer的編譯器編譯后的執行代碼被緩存下來,那么下次調用時只要是相同的預編譯語句就不需要編譯,只要將參數直接傳入編譯過的語句執行代碼中就會得到執行。
- 在statement語句中,即使是相同操作但因為數據內容不一樣,所以整個語句本身不能匹配,沒有緩存語句的意義.事實是沒有數據庫會對普通語句編譯后的執行代碼緩存.這樣每執行一次都要對傳入的語句編譯一次.
- (語法檢查,語義檢查,翻譯成二進制命令,緩存)
- PreparedStatement 可以防止 SQL 注入
DatabaseMetaData類
Java 通過JDBC獲得連接以后,得到一個Connection 對象,可以從這個對象獲得有關數據庫管理系統的各種信息,包括數據庫中的各個表,表中的各個列,數據類型,觸發器,存儲過程等各方面的信息。根據這些信息,JDBC可以訪問一個實現事先并不了解的數據庫。獲取這些信息的方法都是在DatabaseMetaData類的對象上實現的,而DataBaseMetaData對象是Connection對象上調用getMetaData()獲得的。DatabaseMetaData 類中提供了許多方法用于獲得數據源的各種信息,通過這些方法可以非常詳細的了解數據庫的信息:
getURL():返回一個String類對象,代表數據庫的URL。
getUserName():返回連接當前數據庫管理系統的用戶名。
isReadOnly():返回一個boolean值,指示數據庫是否只允許讀操作。
getDatabaseProductName():返回數據庫的產品名稱。
getDatabaseProductVersion():返回數據庫的版本號。
getDriverName():返回驅動驅動程序的名稱。
getDriverVersion():返回驅動程序的版本號。
ResultSetMetaData 類
當有一個結果集rsultSet,但是不知道里面有多少列和每列的名字時,可以使用ResultSetMetaData。還能用于編寫通用的查詢方法。
獲取ResultSetMetaData對象:ResultSetMetaData rsmd = rsultSet.getMetaData();
一些常用的方法:
getColumnName(int column):獲取指定列的名稱
getColumnLabel(int count):獲取指定列的別名,其索引從1開始
getColumnCount():返回當前 ResultSet 對象中的列數。
getColumnTypeName(int column):檢索指定列的數據庫特定的類型名稱。
getColumnDisplaySize(int column):指示指定列的最大標準寬度,以字符為單位。
isNullable(int column):指示指定列中的值是否可以為 null。
isAutoIncrement(int column):指示是否自動為指定列進行編號,這樣這些列仍然是只讀的。
DAO(DATA ACCESS OBJECT)設計模式
實現功能模塊化。更有利于代碼的維護和升級。
訪問數據信息的類,包含了對數據的CRUD(create,read,update,delete)。而不包含任何業務相關的信息。
使用JDBC編寫DAO可能會包含的方法:
//insert,update,delete操作都可以包含在其中
void update(Connection connection,String str,Object ... args);
//查詢一條記錄,返回相應的對象
<T> T get(Class<T> clazz,String sql,Object ... args);
//查詢多條記錄,返回對應的集合
<T> List<T> get(Class<T> clazz,String sql,Object ... args);
//返回某條記錄的某一個字段的值或一個統計的值(一共有多少條記錄等)
<E> E getForValue(String sql,Object ... args);
取得數據庫自動生成的主鍵
通過Connection重載的prepareStatement(sql,flag)方法來獲得包含了主鍵值的preparedStatement對象,然后在用preparedStatement對象的getGeneratedKeys()獲取包含了主鍵的resultSet對象,在resultSet對象中只有一列GRNERATED_KEY,用于存放生成的主鍵值。
Oracle是通過序列生成的主鍵值,不能獲取
Connection conn = JdbcUtil.getConnection();
String sql = "insert into user(name,password,email,birthday)
values('abc','123','abc@sina.com','1978-08-08')";
PreparedStatement st = conn.
prepareStatement(sql,Statement.RETURN_GENERATED_KEYS );
st.executeUpdate();
ResultSet rs = st.getGeneratedKeys(); //得到插入行的主鍵
if(rs.next())
System.out.println(rs.getObject(1));
使用JDBC插入大對象
Oracle LOB
LOB,即Large Objects(大對象),是用來存儲大量的二進制和文本數據的一種數據類型(一個LOB字段可存儲可多達4GB 的數據)。LOB 分為兩種類型:內部LOB和外部LOB。
- 內部LOB將數據以字節流的形式存儲在數據庫的內部。因而內部LOB的許多操作都可以參與事務,也可以像處理普通數據一樣對其進行備份和恢復操作。Oracle支持三種類型的內部LOB:
- BLOB(二進制數據)
- CLOB(單字節字符數據)
- NCLOB(多字節字符數據)
CLOB和NCLOB類型適用于存儲超長的文本數據,BLOB字段適用于存儲大量的二進制數據,如圖像、視頻、音頻,文件等。
- 目前只支持一種外部LOB類型,即BFILE類型。在數據庫內,該類型僅存儲數據在操作系統中的位置信息,而數據的實體以外部文件的形式存在于操作系統的文件系統中。因而,該類型所表示的數據是只讀的,不參與事務。該類型可幫助用戶管理大量的由外部程序訪問的文件。
使用JDBC來寫入Blob型數據到Oracle中
Oracle的Blob字段比long字段的性能要好,可以用來保存如圖片之類的二進制數據。
Oracle的BLOB字段由兩部分組成:數據(值)和指向數據的指針(定位器)。盡管值與表自身一起存儲,但是一個BLOB列并不包含值,僅有它的定位指針。為了使用大對象,程序必須聲明定位器類型的本地變量。
當Oracle內部LOB被創建時,定位器被存放在列中,值被存放在LOB段中,LOB段是在數據庫內部表的一部分。
因為Blob自身有一個cursor,當寫入Blob字段必須使用指針(定位器)對Blob進行操作,因而在寫入Blob之前,必須獲得指針(定位器)才能進行寫入。
如何獲得Blob的指針(定位器) :需要先插入一個empty的blob,這將創建一個blob的指針,然后再把這個empty的blob的指針查詢出來,這樣通過兩步操作,就獲得了blob的指針,可以真正的寫入blob數據了。
步驟
- 插入空blob
insert into javatest(name,content) values(?,empty_blob());
- 獲得blob的cursor
select content from javatest where name= ? for update;
注意: 須加for update
,鎖定該行,直至該行被修改完畢,保證不產生并發沖突。 - 利用 io,和獲取到的cursor往數據庫寫數據流。
MySQL BLOB
MySQL中,BLOB是一個二進制大型對象,是一個可以存儲大量數據的容器,它能容納不同大小的數據。MySQL的四種BLOB類型(除了在存儲的最大信息量上不同外,他們是等同的)。
類型 | 大小 |
---|---|
TinyBlob | 255 |
Blob | 65K |
MidiumBlob | 16M |
LongBlob | 4G |
實際使用中根據需要存入的數據大小定義不同的BLOB類型。需要注意的是:如果存儲的文件過大,數據庫的性能會下降。
//插入 BLOB 類型的數據必須使用 PreparedStatement:因為 BLOB 類型的數據時無法使用字符串拼寫的。
//調用 setBlob(int index, InputStream inputStream)
String sql = "INSERT INTO customers(name, email, birth, picture) VALUES(?,?,?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "ABCDE");
preparedStatement.setString(2, "abcde@atguigu.com");
preparedStatement.setDate(3,new Date(new java.util.Date().getTime()));
InputStream inputStream = new FileInputStream("Hydrangeas.jpg");
preparedStatement.setBlob(4, inputStream);
preparedStatement.executeUpdate();
//讀取 blob 數據:使用 getBlob 方法讀取到 Blob 對象
//調用 Blob 的 getBinaryStream() 方法得到輸入流。再使用 IO 操作即可.
connection = JDBCTools.getConnection();
String sql = "SELECT id, name customerName, email, birth, picture FROM customers WHERE id = 13";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
System.out.println(id + ", " + name + ", " + email);
Blob picture = resultSet.getBlob(5);
InputStream in = picture.getBinaryStream();
System.out.println(in.available());
OutputStream out = new FileOutputStream("flower.jpg");
byte [] buffer = new byte[1024];
int len = 0;
while((len = in.read(buffer)) != -1){
out.write(buffer, 0, len);
}
數據庫事務
在數據庫中,所謂事務是指一組邏輯操作單元,使數據從一種狀態變換到另一種狀態。
為確保數據庫中數據的一致性,數據的操縱應當是離散的成組的邏輯單元:當它全部完成時,數據的一致性可以保持,而當這個單元中的一部分操作失敗,整個事務應全部視為錯誤,所有從起始點以后的操作應全部回退到開始狀態。
事務的操作:先定義開始一個事務,然后對數據作修改操作,這時如果提交(COMMIT),這些修改就永久地保存下來,如果回退(ROLLBACK),數據庫管理系統將放棄所作的所有修改而回到開始事務時的狀態。
事務的ACID(acid)屬性:
- 原子性(Atomicity):原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。
- 一致性(Consistency):事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。
- 隔離性(Isolation):事務的隔離性是指一個事務的執行不能被其他事務干擾,即一個事務內部的操作及使用的數據對并發的其他事務是隔離的,并發執行的各個事務之間不能互相干擾。
- 持久性(Durability):持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來的其他操作和數據庫故障不應該對其有任何影響。
如果多個操作, 每個操作使用的是自己的單獨的連接, 則無法保證事務.。
具體步驟:
1). 事務操作開始前, 開始事務:取消 Connection 的默認提交行為。connection.setAutoCommit(false);
2). 如果事務的操作都成功,則提交事務: connection.commit();
3). 回滾事務: 若出現異常, 則在 catch 塊中回滾事務:
try {
//設置取消默認提交
connection.setAutoCommit(false);
...
// 提交事務
connection.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滾事務
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {...}
數據庫的隔離級別
對于同時運行的多個事務, 當這些事務訪問數據庫中相同的數據時, 如果沒有采取必要的隔離機制, 就會導致各種并發問題:
- 臟讀:對于兩個事物 T1、 T2, T1 讀取了已經被 T2 更新但還沒有被提交的字段. 之后, 若 T2 回滾, T1讀取的內容就是臨時且無效的.
- 不可重復讀: 對于兩個事物 T1、 T2, T1 讀取了一個字段, 然后 T2 更新了該字段. 之后, T1再次讀取同一個字段, 值就不同了.
- 幻讀:對于兩個事物 T1、T2, T1 從一個表中讀取了一個字段, 然后 T2 在該表中插入了一些新的行. 之后, 如果 T1 再次讀取同一個表, 就會多出幾行.
數據庫事務的隔離性: 數據庫系統必須具有隔離并發運行各個事務的能力, 使它們不會相互影響, 避免各種并發問題。
一個事務與其他事務隔離的程度稱為隔離級別. 數據庫規定了多種事務隔離級別, 不同隔離級別對應不同的干擾程度, 隔離級別越高, 數據一致性就越好, 但并發性越弱。
數據庫提供的 4 種事務隔離級別:
隔離級別 | 描述 |
---|---|
READ UNCOMMITTED(讀未提交數據) | 允許事務讀取未被其它事務提交的變更,臟讀、不可重復讀、幻讀都會出現 |
READ COMMITTED(讀已提交數據) | 只允許讀取事務已經提交的變更,可避免臟讀,但不可重復讀、幻讀仍會出現 |
REPEATABLE READ(可重復讀) | 確保事務可以多次從一個字段中讀取相同的值,但在這個事務的持續期間,禁止其它事務對這個字段進行更新,可避免臟讀和不可重復讀,但幻讀問題仍存在 |
SERIALIZABLE(串行化) | 確保事務可以從一個表中讀取相同的行,在這個事務持續的期間,禁止其它事務對該表進行插入、更改和刪除操作,所有的并發問題都可避免,但性能十分低下 |
Oracle 支持的 2 種事務隔離級別:READ COMMITED, SERIALIZABLE. Oracle 默認的事務隔離級別為: READ COMMITED
Mysql 支持 4 中事務隔離級別. Mysql 默認的事務隔離級別為: REPEATABLE READ
在 MySql 中設置隔離級別
每啟動一個 mysql 程序, 就會獲得一個單獨的數據庫連接. 每個數據庫連接都有一個全局變量 @@tx_isolation, 表示當前的事務隔離級別. MySQL 默認的隔離級別為 Repeatable Read
查看當前的隔離級別: SELECT @@tx_isolation;
設置當前 mySQL 連接的隔離級別:set transaction isolation level read committed;
設置數據庫系統的全局的隔離級別:set global transaction isolation level read committed;
批量處理JDBC語句提高處理速度
當需要成批插入或者更新記錄時。可以采用Java的批量更新機制,這一機制允許多條語句一次性提交給數據庫批量處理。通常情況下比單獨提交處理更有效率。
JDBC的批量處理語句包括下面兩個方法:
- addBatch(String):添加需要批量處理的SQL語句或是參數;
- executeBatch():執行批量處理語句;
通常我們會遇到兩種批量執行SQL語句的情況:
- 多條SQL語句的批量處理;
- 一個SQL語句的批量傳參;
多條SQL語句批量處理
...
Statement st = connection.createStatement();
st.addBatch(sql1);
st.addBatch(sql2);
...
st.excuteBatch();
...
一個SQL語句批量傳參
...
PreparedStatement pst = connection.preparedStatement(sql);
for(int i = 0;i<n;i++){
pst.setInt(1,i);
...
pst.addBatch();
}
pst.excuteBatch();
...
JDBC數據庫連接池的必要性
在使用開發基于數據庫的web程序時,傳統的模式基本是按以下步驟:
- 在主程序(如servlet、beans)中建立數據庫連接。
- 進行sql操作
- 斷開數據庫連接。
這種模式開發,存在的問題:
- 普通的JDBC數據庫連接使用 DriverManager 來獲取,每次向數據庫建立連接的時候都要將 Connection 加載到內存中,再驗證用戶名和密碼(得花費0.05s~1s的時間)。需要數據庫連接的時候,就向數據庫要求一個,執行完成后再斷開連接。這樣的方式將會消耗大量的資源和時間。數據庫的連接資源并沒有得到很好的重復利用。若同時有幾百人甚至幾千人在線,頻繁的進行數據庫連接操作將占用很多的系統資源,嚴重的甚至會造成服務器的崩潰。
- 對于每一次數據庫連接,使用完后都得斷開。否則,如果程序出現異常而未能關閉,將會導致數據庫系統中的內存泄漏,最終將導致重啟數據庫。
- 這種開發不能控制被創建的連接對象數,系統資源會被毫無顧及的分配出去,如連接過多,也可能導致內存泄漏,服務器崩潰。
數據庫連接池(connection pool)
為解決傳統開發中的數據庫連接問題,可以采用數據庫連接池技術。
數據庫連接池的基本思想就是為數據庫連接建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接,當需要建立數據庫連接時,只需從“緩沖池”中取出一個,使用完畢之后再放回去。
數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而不是重新建立一個。
數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中,這些數據庫連接的數量是由最小數據庫連接數來設定的。無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數量。連接池的最大數據庫連接數量限定了這個連接池能占有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中。
數據庫連接池技術的優點
- 資源重用:
由于數據庫連接得以重用,避免了頻繁創建,釋放連接引起的大量性能開銷。在減少系統消耗的基礎上,另一方面也增加了系統運行環境的平穩性。 - 更快的系統反應速度
數據庫連接池在初始化過程中,往往已經創建了若干數據庫連接置于連接池中備用。此時連接的初始化工作均已完成。對于業務請求處理而言,直接利用現有可用連接,避免了數據庫連接初始化和釋放過程的時間開銷,從而減少了系統的響應時間。 - 新的資源分配手段
對于多應用共享同一數據庫的系統而言,可在應用層通過數據庫連接池的配置,實現某一應用最大可用數據庫連接數的限制,避免某一應用獨占所有的數據庫資源。 - 統一的連接管理,避免數據庫連接泄露
在較為完善的數據庫連接池實現中,可根據預先的占用超時設定,強制回收被占用連接,從而避免了常規數據庫連接操作中可能出現的資源泄露。
兩種開源的數據庫連接池
- JDBC 的數據庫連接池使用 javax.sql.DataSource 來表示,DataSource 只是一個接口,該接口通常由服務器(Weblogic, WebSphere, Tomcat)提供實現,也有一些開源組織提供實現:
- DBCP 數據庫連接池
- C3P0 數據庫連接池
- DataSource 通常被稱為數據源,它包含連接池和連接池管理兩個部分,習慣上也經常把 DataSource 稱為連接池
DBCP 數據源
- DBCP 是 Apache 軟件基金組織下的開源連接池實現,該連接池依賴該組織下的另一個開源系統:Common-pool. 如需使用該連接池實現,應在系統中增加如下兩個 jar 文件:
- Commons-dbcp.jar:連接池的實現
- Commons-pool.jar:連接池實現的依賴庫
- Tomcat 的連接池正是采用該連接池來實現的。該數據庫連接池既可以與應用服務器整合使用,也可由應用程序獨立使用。
數據源和數據庫連接不同,數據源無需創建多個,它是產生數據庫連接的工廠,因此整個應用只需要一個數據源即可。
當數據庫訪問結束后,程序還是像以前一樣關閉數據庫連接:conn.close();
但上面的代碼并沒有關閉數據庫的物理連接,它僅僅把數據庫連接釋放,歸還給了數據庫連接池。
C3P0 數據源
Apache—DBUtils簡介
- commons-dbutils 是 Apache 組織提供的一個開源 JDBC工具類庫,它是對JDBC的簡單封裝,學習成本極低,并且使用dbutils能極大簡化jdbc編碼的工作量,同時也不會影響程序的性能。
- API介紹:
- org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSetHandler
- 工具類
- org.apache.commons.dbutils.DbUtils、。
DbUtils類
DbUtils :提供如關閉連接、裝載JDBC驅動程序等常規工作的工具類,里面的所有方法都是靜態的。主要方法如下:
-
public static void close(…) throws java.sql.SQLException
: DbUtils類提供了三個重載的關閉方法。這些方法檢查所提供的參數是不是NULL,如果不是的話,它們就關閉Connection、Statement和ResultSet。 -
public static void closeQuietly(…)
: 這一類方法不僅能在Connection、Statement和ResultSet為NULL情況下避免關閉,還能隱藏一些在程序中拋出的SQLEeception。 -
public static void commitAndCloseQuietly(Connection conn)
: 用來提交連接,然后關閉連接,并且在關閉連接時不拋出SQL異常。 -
public static boolean loadDriver(java.lang.String driverClassName)
:這一方裝載并注冊JDBC驅動程序,如果成功就返回true。使用該方法,你不需要捕捉這個異常ClassNotFoundException。
QueryRunner類
- 該類簡單化了SQL查詢,它與ResultSetHandler組合在一起使用可以完成大部分的數據庫操作,能夠大大減少編碼量。
- QueryRunner類提供了兩個構造方法:
- 默認的構造方法
- 需要一個 javax.sql.DataSource 來作參數的構造方法。
- QueryRunner類的主要方法:
-
public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException
:執行一個查詢操作,在這個查詢中,對象數組中的每個元素值被用來作為查詢語句的置換參數。該方法會自行處理 PreparedStatement 和 ResultSet 的創建和關閉。 -
public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException
: 幾乎與第一種方法一樣;唯一的不同在于它不將數據庫連接提供給方法,并且它是從提供給構造方法的數據源(DataSource) 或使用的setDataSource 方法中重新獲得 Connection。 -
public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException
: 執行一個不需要置換參數的查詢操作。 -
public int update(Connection conn, String sql, Object[] params) throws SQLException
:用來執行一個更新(插入、更新或刪除)操作。 -
public int update(Connection conn, String sql) throws SQLException
:用來執行一個不需要置換參數的更新操作。
-
ResultSetHandler接口
該接口用于處理 java.sql.ResultSet,將數據按要求轉換為另一種形式。
ResultSetHandler 接口提供了一個單獨的方法:Object handle (java.sql.ResultSet .rs)。
ResultSetHandler 接口的實現類:
- ArrayHandler:把結果集中的第一行數據轉成對象數組。
- ArrayListHandler:把結果集中的每一行數據都轉成一個數組,再存放到List中。
- BeanHandler:將結果集中的第一行數據封裝到一個對應的JavaBean實例中。
- BeanListHandler:將結果集中的每一行數據都封裝到一個對應的JavaBean實例中,存放到List里。
- ColumnListHandler:將結果集中某一列的數據存放到List中。
- KeyedHandler(name):將結果集中的每一行數據都封裝到一個Map里,再把這些map再存到一個map里,其key為指定的key。
- MapHandler:將結果集中的第一行數據封裝到一個Map里,key是列名,value就是對應的值。
- MapListHandler:將結果集中的每一行數據都封裝到一個Map里,然后再存放到List
數據庫的分頁語句
在編寫Web應用程序等系統時,會涉及到與數據庫的交互,如果數據庫中數據量很大的話,一次檢索所有的記錄,會占用系統很大的資源,因此常常采用分頁語句:需要多少數據就只從數據庫中取多少條記錄。以下是Sql Server,Oracle和MySQL的分頁語句(從數據庫表中的第M條數據開始取N條記錄):
MySQL數據庫
My sql數據庫最簡單,是利用mySQL的LIMIT函數,LIMIT [offset,] rows從數據庫表中M條記錄開始檢索N條記錄的語句為:
SELECT [列名列表] FROM 表名稱 LIMIT M,N
例如從表Sys_option(主鍵為sys_id)中從10條記錄還是檢索20條記錄,語句如下:
select * from sys_option limit 10,20
Oralce數據庫
從數據庫表中第M條記錄開始檢索N條記錄
select * from(
select ROWNUM rownum_,t1.* from(
select * from table order by table.id desc
) row_
where rownum <= ?
)
where rownum_ > ?
例如從表employees(主鍵為employee_id)中從11條記錄還是檢索20條記錄,語句如下: