JDBC詳細說明
一、基礎知識
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說明:
ResultSet的Type |
說明 |
</tr>
|||||||||||||||||||||||||||||||
TYPE_FORWARD_ONLY |
結果集不能滾動,只可向前滾動 |
</tr>
|||||||||||||||||||||||||||||||
TYPE_SCROLL_INSENSITIVE |
雙向滾動,但不及時更新,就是如果數據庫里的數據修改過,并不在ResultSet中反應出來 |
</tr>
|||||||||||||||||||||||||||||||
TYPE_SCROLL_SENSITIVE |
雙向滾動,并及時跟蹤數據庫的更新,以便更改ResultSet中的數據 |
</tr>
</tbody>
</table>
ResultSet的Concurrency(并發類型) |
說明 |
</tr>
|||||||||||||||||||||||||
CONCUR_READ_ONLY |
結果集不可用于更新數據庫 |
</tr>
|||||||||||||||||||||||||
CONCUR_UPDATABLE |
結果集可以用于更新數據庫 | </tr> </tbody> </table>