JDBC詳細說明

jopen 11年前發布 | 67K 次閱讀 JDBC Java開發

一、基礎知識

1. 數據持久化

持久化(persistence):對象在內存中創建后,不能永久存在。把對象永久的保存起來就是持久化的過程。而持久化的實現過程大多通過各種關系數據庫來完成。

持久化的主要應用是將內存中的數據存儲在關系型數據庫中,當然也可以存儲在磁盤文件、XML數據文件中。

2. 在java中數據庫存儲技術分類

JDBC直接訪問數據庫。

第三方O/R工具,如Hibernate,mybatis。這些工具都是對JDBC的封裝。

3. JDBC概念

JDBC(Java Datebase Connectivity)是一個獨立于特定數據庫管理系統、通用的sql數據庫存取和操作的公共接口。它是JAVA語言訪問數據庫的一種標準。

4. JDBC常用(重要)類/接口

Java.sql.Driver接口是所有JDBC驅動程序需要實現的接口。這個接口是提供給數據庫廠商使用的,不同數據庫廠商提供不用的實現。

在程序中不需要直接去訪問實現了Driver接口的類,而是由驅動程序管理器類(java.sql.DriverManager)去調用這些Driver實現。

DriverManager類,用來創建連接,它本身就是一個創建Connection的工廠,設計的時候使用的就是Factory模式,給各數據庫廠商提供接口,各數據庫廠商需要實現它;

Connection接口,根據提供的不同驅動產生不同的連接;

Statement接口,用來發送SQL語句;

Resultset接口,用來接收查詢語句返回的查詢結果。

5. JDBC應用步驟

1.注冊加載一個驅動
2.創建數據庫連接(Connection)
3.創建statement,發送sql語句
4.執行sql語句
5.處理sql結果
6.關閉statement和connection

二、加載與注冊驅動

加載 JDBC 驅動需調用 Class 類的靜態方法 forName(),向其傳遞要加載的 JDBC 驅動的類名:

Class.forName(driver);
如:
注冊MYSQL數據庫驅動器

Class.forName("com.mysql.jdbc.Driver");
注冊ORACLE數據庫驅動器

Class.forName("oracle.jdbc.driver.OracleDriver");

三、建立連接

可以調用 DriverManager 類的 getConnection(…….) 方法建立到數據庫的連接:

Connection conn = DriverManager.getConnection(url,uid,pwd);

JDBC URL 用于標識一個被注冊的驅動程序,驅動程序管理器通過這個 URL 選擇正確的驅動程序,從而建立到數據庫的連接。

JDBC URL的標準由三部分組成,各部分間用冒號“:”分隔。 

JDBC URL格式:

協議:<子協議>:<子名稱>
說明:
協議:JDBC URL中的協議總是jdbc 
子協議:子協議用于標識一個數據庫驅動程序
子名稱:一種標識數據庫的方法。子名稱可以依不同的子協議而變化,用子名稱的目的是為了定位數據庫提供足夠的信息 

jdbc:<子協議>:<子名稱>:是一個JNI方式的命名 
注:JNI是Java Native Interface的縮寫。從Java 1.1開始,Java Native Interface (JNI)標準成為java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。

如:
mysql的JDBC URL: jdbc:mysql://localhost:3306/mydbname 
oracle的JDBC URL: jdbc:oracle:thin: @localhost :1521:mydbname

四、訪問數據庫

數據庫連接被用于向數據庫服務器發送命令和 SQL 語句,在連接建立后,需要對數據庫進行訪問,執行 sql 語句。
在 java.sql 包中有 3 個接口分別定義了對數據庫的調用的不同方式:
Statement
    PrepatedStatement
        CallableStatement

1. 用Statement來執行sql語句

Statement對象用于執行靜態的 SQL 語句,并且返回執行結果。

通過調用 Connection 對象的 createStatement 方法創建該對象:

Statement sm = conn.createStatement();

Statement 接口中定義了下列方法用于執行 SQL 語句:

sm.executeQuery(sql); // 執行數據查詢語句(select)
sm.executeUpdate(sql); // 執行數據更新語句(delete、update、insert、drop等)

2. 用PreparedStatement來執行sql語句

PreparedStatement 接口是 Statement 的子接口,它表示一條預編譯過的 SQL 語句。

可以通過調用 Connection 對象的 preparedStatement() 方法獲取 PreparedStatement 對象:

String  sql  = "INSERT INTO user (id,name) VALUES (?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 1);
ps.setString(2, "admin");

ResultSet rs = ps.executeQuery(); // 查詢 int c = ps.executeUpdate(); // 更新</pre>

PreparedStatement 對象所代表的 SQL 語句中的參數用問號(?)來表示,調用 PreparedStatement 對象的 setXXX() 方法來設置這些參數。 setXXX() 方法有兩個參數,第一個參數是要設置的 SQL 語句中的參數的索引(從 1 開始),第二個是設置的 SQL 語句中的參數的值。

3. PreparedStatement與Statement比較

(1)使用PreparedStatement,代碼的可讀性和可維護性Statement高

(2)PreparedStatement 能最大可能提高性能。
DBServer會對預編譯語句提供性能優化。因為預編譯語句有可能被重復調用,所以語句在被DBServer的編譯器編譯后的執行代碼被緩存下來,那么下次調用時只要是相同的預編譯語句就不需要編譯,只要將參數直接傳入編譯過的語句執行代碼中就會得到執行。

在statement語句中,即使是相同操作但因為數據內容不一樣,所以整個語句本身不能匹配,沒有緩存語句的意義。事實是沒有數據庫會對普通語句編譯后的執行代碼緩存。這樣每執行一次都要對傳入的語句編譯一次。 

(3)PreparedStatement能保證安全性,但 Statement有sql注入等安全問題

4. Statement不安全性

SQL 注入是利用某些系統沒有對用戶輸入的數據進行充分的檢查,而在用戶輸入數據中注入非法的 SQL 語句段或命令,從而利用系統的 SQL 引擎完成惡意行為的做法。如下代碼:

String username="a' or 1=1 or 1='";
String psw="b";
String sql = "select count(*) from t_user where username='"+username+"' and psw='"+psw+"'";
sql語句如下:

select count(*) from t_user where username='a' or 1=1 or 1='' and psw='b'
用 PreparedStatement 取代 Statement 就可以解決。

5. 使用Callable Statement

當不直接使用SQL語句,而是調用數據庫中的存儲過程時,要用到Callable Statement。

CallabelStatement從PreparedStatement繼承。

例如:

String sql = "{call insert_users(?,?)}";
// 調用存儲過程
CallableStatement st = conn.prepareCall(sql);
st.setInt(1, 1);
st.setString(2, "admin");
// 在此 CallableStatement對象中執行 SQL 語句,該語句可以是任何種類的 SQL 語句。
st.execute();

五、處理執行結果

查詢語句,返回記錄集ResultSet。
更新語句,返回數字,表示該更新影響的記錄數。

ResultSet:
ResultSet 對象以邏輯表格的形式封裝了執行數據庫操作的結果集,ResultSet 接口由數據庫廠商實現。

ResultSet 接口的常用方法:
next():將游標往后移動一行,如果成功返回true;否則返回false。ResultSet 對象維護了一個指向當前數據行的游標,初始的時候,游標在第一行之前,可以通過 ResultSet 對象的 next() 方法移動到下一行。
getXxx(String name):返回當前游標下某個字段的值。如:getInt("id")或getSting("name")。

六、釋放數據庫連接

rs.close();

ps.close(); 或者 stat.close();

conn.close();

一般是在finally里面進行釋放資源。

七、數據庫事務

1. 概述

在數據庫中,所謂事務是指一組邏輯操作單元,使數據從一種狀態變換到另一種狀態。

為確保數據庫中數據的一致性,數據的操縱應當是離散的成組的邏輯單元:當它全部完成時,數據的一致性可以保持,而當這個單元中的一部分操作失敗,整個事務應全部視為錯誤,所有從起始點以后的操作應全部回退到開始狀態。 

事務的操作:先定義開始一個事務,然后對數據作修改操作,這時如果提交(COMMIT),這些修改就永久地保存下來,如果回退(ROLLBACK),數據庫管理系統將放棄您所作的所有修改而回到開始事務時的狀態。

2. 事務的ACID屬性

2.1 原子性(Atomicity)

原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。 

2.2 一致性(Consistency)

事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。(數據不被破壞)

2.3 隔離性(Isolation)

事務的隔離性是指一個事務的執行不能被其他事務干擾,即一個事務內部的操作及使用的數據對并發的其他事務是隔離的,并發執行的各個事務之間不能互相干擾。

2.4 持久性(Durability)

持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來的其他操作和數據庫故障不應該對其有任何影響。

3. JDBC 事務處理

在JDBC中,事務默認是自動提交的,每次執行一個 SQL 語句時,如果執行成功,就會向數據庫自動提交,而不能回滾。

為了讓多個 SQL 語句作為一個事務執行,需調用 Connection 對象的 setAutoCommit(false); 以取消自動提交事務:

conn.setAutoCommit(false);
在所有的 SQL 語句都成功執行后,調用 commit(); 方法提交事務

conn.commit();
在出現異常時,調用 rollback(); 方法回滾事務,一般再catch模塊中執行回滾操作。

conn.rollback();

可以通過Connection的getAutoCommit()方法來獲得當前事務的提交方式。

注意:在MySQL中的數據庫存儲引擎InnoDB支持事務,MyISAM不支持事務。

八、批量處理JDBC語句

1. 概述

當需要批量插入或者更新記錄時。可以采用Java的批量更新機制,這一機制允許多條語句一次性提交給數據庫批量處理。通常情況下比單獨提交處理更有效率。

JDBC的批量處理語句包括下面兩個方法:
addBatch(String):添加需要批量處理的SQL語句或是參數;
executeBatch();執行批量處理語句;

通常我們會遇到兩種批量執行SQL語句的情況:
多條SQL語句的批量處理;
一個SQL語句的批量傳參; 

2. Statement批量處理

Statement sm = conn.createStatement();
sm.addBatch(sql1);
sm.addBatch(sql2);
...
//批量處理
sm.executeBatch()
//清除sm中積攢的參數列表
sm.clearBatch();

3. PreparedStatement批量傳參

PreparedStatement ps = conn.preparedStatement(sql);
for(int i=1;i<100000;i++){
    ps.setInt(1, i);
    ps.setString(2, "name"+i);
    ps.setString(3, "email"+i);
    ps.addBatch();
    if((i+1)%1000==0){
        //批量處理
        ps.executeBatch();
        //清空ps中積攢的sql
        ps.clearBatch();
    }
}

注意:MySQL不支持批量處理。

批量處理應該設置一個上限,當批量處理列表中的sql累積到一定數量后,就應該執行,并在執行完成后,清空批量列表。

一般再excel導入數據的時候會用到批處理。

九、使用 JDBC 處理元數據 

1. 概述

Java 通過JDBC獲得連接以后,得到一個Connection 對象,可以從這個對象獲得有關數據庫管理系統的各種信息,包括數據庫中的各個表,表中的各個列,數據類型,觸發器,存儲過程等各方面的信息。根據這些信息,JDBC可以訪問一個實現事先并不了解的數據庫。

獲取這些信息的方法都是在DatabaseMetaData類的對象上實現的,而DataBaseMetaData對象是在Connection對象上獲得的。

2. 獲取數據庫元數據 

DatabaseMetaData 類中提供了許多方法用于獲得數據源的各種信息,通過這些方法可以非常詳細的了解數據庫的信息:

getURL():返回一個String類對象,代表數據庫的URL。
getUserName():返回連接當前數據庫管理系統的用戶名。
isReadOnly():返回一個boolean值,指示數據庫是否只允許讀操作。
getDatabaseProductName():返回數據庫的產品名稱。
getDatabaseProductVersion():返回數據庫的版本號。
getDriverName():返回驅動驅動程序的名稱。
getDriverVersion():返回驅動程序的版本號。

3. ResultSetMetaData

可用于獲取關于 ResultSet 對象中列的類型和屬性信息的對象:

getColumnName(int column):獲取指定列的名稱
getColumnCount():返回當前 ResultSet 對象中的列數。 
getColumnTypeName(int column):檢索指定列的數據庫特定的類型名稱。 
getColumnDisplaySize(int column):指示指定列的最大標準寬度,以字符為單位。 
isNullable(int column):指示指定列中的值是否可以為 null。 
isAutoIncrement(int column):指示是否自動為指定列進行編號,這樣這些列仍然是只讀的。

十、創建可滾動、更新的記錄集

1. Statement

Statement stmt = conn.createStatement(type,concurrency);

2. PreparedStatement

PreparedStatement stmt = conn.prepareStatement(sql,type,concurrency);

type說明:

</tr>

</tr>

</tr>

</tr> </tbody> </table>
Concurrency(并發類型)說明:
ResultSet的Type
說明
TYPE_FORWARD_ONLY
結果集不能滾動,只可向前滾動
TYPE_SCROLL_INSENSITIVE
雙向滾動,但不及時更新,就是如果數據庫里的數據修改過,并不在ResultSet中反應出來
TYPE_SCROLL_SENSITIVE
雙向滾動,并及時跟蹤數據庫的更新,以便更改ResultSet中的數據

</tr>

</tr>

</tr> </tbody> </table>

3. ResultSet滾動的結果集使用

First:將指針移動到此 ResultSet 對象的第一行 
Last:將指針移動到此 ResultSet 對象的最后一行 
beforeFirst:將指針移動到此 ResultSet 對象的開頭,正好位于第一行之前 
afterLast:將指針移動到此 ResultSet 對象的末尾,正好位于最后一行之后 
isFirst:檢索指針是否位于此 ResultSet 對象的第一行 
isLast:檢索指針是否位于此 ResultSet 對象的最后一行 
isBeforeFirst:檢索指針是否位于此 ResultSet 對象的第一行之前 
isAfterLast:檢索指針是否位于此 ResultSet 對象的最后一行之后 
Relative:按相對行數(或正或負)移動指針 
Next:將指針從當前位置下移一行 
Previous:將指針移動到此 ResultSet 對象的上一行 
Absolute:將指針移動到此 ResultSet 對象的給定行編號 

如:

rs.absolute(80); //將指針移動到ResultSet 對象的第80行記錄。

注意:該特性對Oralce數據有效。但是在Mysql數據庫中無效,Mysql只支持TYPE_SCROLL_INSENSITIVE,CONCUR_READ_ONLY。

十一、JDBC連接池

1. 為什么要使用JDBC連接池

普通的JDBC數據庫連接使用 DriverManager 來獲取,每次向數據庫建立連接的時候都要將 Connection 加載到內存中,再驗證用戶名和密碼。需要數據庫連接的時候,就向數據庫要求一個,執行完成后再斷開連接。這樣的方式將會消耗大量的資源和時間。數據庫的連接資源并沒有得到很好的重復利用.若同時有幾百人甚至幾千人在線,頻繁的進行數據庫連接操作將占用很多的系統資源,嚴重的甚至會造成服務器的崩潰。
對于每一次數據庫連接,使用完后都得斷開。否則,如果程序出現異常而未能關閉,將會導致數據庫系統中的內存泄漏,最終將導致重啟數據庫。
這種開發不能控制被創建的連接對象數,系統資源會被毫無顧及的分配出去,如連接過多,也可能導致內存泄漏,服務器崩潰。

2. 數據庫連接池(connection pool)

為解決傳統開發中的數據庫連接問題,可以采用數據庫連接池技術。
數據庫連接池的基本思想就是為數據庫連接建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接,當需要建立數據庫連接時,只需從“緩沖池”中取出一個,使用完畢之后再放回去。
數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而不是重新建立一個。
數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中,這些數據庫連接的數量是由最小數據庫連接數來設定的。無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數量。連接池的最大數據庫連接數量限定了這個連接池能占有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中。

3. 數據庫連接池工作原理

JDBC詳解

4. 使用數據庫連接池的優點

(1)資源重用:
由于數據庫連接得以重用,避免了頻繁創建,釋放連接引起的大量性能開銷。在減少系統消耗的基礎上,另一方面也增加了系統運行環境的平穩性。
(2)更快的系統反應速度
數據庫連接池在初始化過程中,往往已經創建了若干數據庫連接置于連接池中備用。此時連接的初始化工作均已完成。對于業務請求處理而言,直接利用現有可用連接,避免了數據庫連接初始化和釋放過程的時間開銷,從而減少了系統的響應時間。
(3)新的資源分配手段
對于多應用共享同一數據庫的系統而言,可在應用層通過數據庫連接池的配置,實現某一應用最大可用數據庫連接數的限制,避免某一應用獨占所有的數據庫資源。
(4)統一的連接管理,避免數據庫連接泄露
在較為完善的數據庫連接池實現中,可根據預先的占用超時設定,強制回收被占用連接,從而避免了常規數據庫連接操作中可能出現的資源泄露。

5. 常用數據庫連接池介紹

JDBC 的數據庫連接池使用 javax.sql.DataSource 來表示,DataSource 只是一個接口,該接口通常由服務器(Weblogic, WebSphere, Tomcat)提供實現,也有一些開源組織提供實現,如:
DBCP 數據庫連接池
C3P0 數據庫連接池
Proxpool 數據庫連接池

其中,DBCP和C3P0用得比較多。使用時需加入dbcp和pool包。

Tomcat 在 7.0 以前的版本都是使用 commons-dbcp 做為連接池的實現。

數據源和數據庫連接不同,數據源無需創建多個,它是產生數據庫連接的工廠,因此整個應用只需要一個數據源即可。

當數據庫訪問結束后,程序還是像以前一樣關閉數據庫連接:conn.close(); 但上面的代碼并沒有關閉數據庫的物理連接,它僅僅把數據庫連接釋放,歸還給了數據庫連接池。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
ResultSet的Concurrency(并發類型)
說明
CONCUR_READ_ONLY
結果集不可用于更新數據庫
CONCUR_UPDATABLE
結果集可以用于更新數據庫
  • sesese色