更多內容,歡迎關注微信公眾號:全菜工程師小輝~
前幾天筆者發布了博客,手寫mybatis徹底搞懂框架原理。為了幫助初學者更好理解mybatis框架,這次講解一下JAVA的JDBC的運行過程。
JDBC的作用
JDBC的全稱是Java DataBase Connection,也就是Java數據庫連接,我們可以用它來操作關系型數據庫。JDBC接口及相關類在java.sql包和javax.sql包里。我們可以用它來連接數據庫,執行SQL查詢,存儲過程,并處理返回的結果。
JDBC接口讓Java程序和JDBC驅動實現了松耦合,使得切換不同的數據庫變得更加簡單。
JDBC
JDBC的連接步驟
執行一次JDBC連接,分六個步驟進行:
1. 導入包
在程序中包含數據庫編程所需的JDBC類。大多數情況下,使用 import java.sql.* 就足夠了
2. 注冊JDBC驅動程序
需要初始化驅動程序,這樣就可以打開與數據庫的通信。
3. 打開一個連接
使用DriverManager.getConnection()方法來創建一個Connection對象,它代表一個數據庫的物理連接。
4. 執行一個查詢
需要使用一個類型為Statement或PreparedStatement的對象(兩者區別看后文),并提交一個SQL語句到數據庫執行查詢。
5. 從結果集中提取數據
這一步中演示如何從數據庫中獲取查詢結果的數據。使用ResultSet.getXXX()方法來檢索的數據結果
6. 清理環境資源
在使用JDBC與數據交互操作數據庫中的數據后,應該明確地關閉所有的數據庫資源以減少資源的浪費。本文使用了try with resources方式關閉資源,這是JDK7的語法糖,讀者可自行搜索。
完整代碼如下。
//STEP 1. 導入包 import java.sql.*; class JDBCExample { // JDBC驅動包名和數據庫的URL static final String JDBC_DRIVER = "com.MySQL.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://localhost/test"; // 數據庫名和密碼自己修改 static final String USER = "username"; static final String PASS = "password"; public static void main(String[] args) { String sql = "SELECT id, first, last, age FROM Employees"; //STEP 2: 注冊JDBC驅動程序 try { Class.forName(JDBC_DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } // try with resources方式關閉資源。 //STEP 6: 清理環境資源 try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { //STEP 3: 打開一個連接 System.out.println("Connecting to database..."); // Connection conn = DriverManager.getConnection(DB_URL, USER, PASS); //STEP 4: 執行一個查詢 System.out.println("Creating statement..."); // Statement stmt = conn.createStatement(); // ResultSet rs = stmt.executeQuery(sql); //STEP 5: 從結果集中提取數據 while (rs.next()) { // 根據列名獲取數據 int id = rs.getInt("id"); int age = rs.getInt("age"); String first = rs.getString("first"); String last = rs.getString("last"); // 顯示結果 System.out.print("ID: " + id); System.out.print(", Age: " + age); System.out.print(", First: " + first); System.out.println(", Last: " + last); } } catch (SQLException se) { // 處理可能出現的錯誤 se.printStackTrace(); } System.out.println("Goodbye!"); } }
JDBC的最佳實踐
- 數據庫資源是非常昂貴的,用完了應該盡快關閉它。Connection, Statement, ResultSet等JDBC對象都有close方法,調用它就好了。
- 在代碼中必須顯式關閉掉ResultSet,Statement,Connection,如果你用的是連接池的話,連接用完后會放回池里,但是沒有關閉的ResultSet和Statement就會造成資源泄漏了。
- 在finally塊中關閉資源,保證即便出了異常也能正常關閉。
- 大量相似的查詢應當使用批處理完成。
- 盡量使用PreparedStatement而不是Statement,以避免SQL注入,同時還能通過預編譯和緩存機制提升執行的效率。
- 如果你要將大量數據讀入到ResultSet中,應該合理的設置fetchSize以便提升性能。
- 你用的數據庫可能沒有支持所有的隔離級別,用之前先仔細確認下。
- 數據庫隔離級別越高性能越差,確保你的數據庫連接設置的隔離級別是最優的。
- 如果你需要長時間對ResultSet進行操作的話,盡量使用離線的RowSet。
FAQ
JDBC是如何實現Java程序和JDBC驅動的松耦合?
JDBC API使用Java的反射機制來實現Java程序和JDBC驅動的松耦合。看一下上文的JDBC示例,你會發現所有操作都是通過JDBC接口完成的,而驅動只有在通過Class.forName反射機制來加載的時候才會出現。
這是Java核心庫里反射機制的最佳實踐之一,它使得應用程序和驅動程序之間進行了隔離,讓遷移數據庫的工作變得更簡單。
Statement和PreparedStatement區別
- 關系:PreparedStatement繼承自Statement,兩者都是接口
- 區別:PreparedStatement可以使用占位符,而且是預編譯的,批處理比Statement效率高
預編譯
創建時的區別:
Statement statement = conn.createStatement(); PreparedStatement preStatement = conn.prepareStatement(sql);
執行時的區別:
ResultSet rSet = statement.executeQuery(sql); ResultSet pSet = preStatement.executeQuery();
由上可以看出,PreparedStatement有預編譯的過程,已經綁定sql,之后無論執行多少次,都不會再去進行編譯,而Statement 不同,如果執行多次,則相應的就要編譯多少次sql,所以從這點看,PreparedStatement的效率會比Statement要高一些。PreparedStatement是預編譯的,所以可以有效的防止SQL注入等問題
占位符
PrepareStatement可以替換變量在SQL語句中可以包含?,可以用?替換成變量。
ps = conn.prepareStatement("select * from Employees where id=?"); int sid = 1001; ps.setInt(1, sid); rs = ps.executeQuery();
而Statement只能用字符串拼接。
int sid = 1001; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from Employees where id=" + sid);
JDBC的ResultSet
在查詢數據庫后會返回一個ResultSet,它就像是查詢結果集的一張數據表。
ResultSet對象維護了一個游標,指向當前的數據行。開始的時候這個游標指向的是第一行。如果調用了ResultSet的next()方法游標會下移一行,如果沒有更多的數據了,next()方法會返回false。可以在for循環中用它來遍歷數據集。
默認的ResultSet是不能更新的,游標也只能往下移。也就是說你只能從第一行到最后一行遍歷一遍。不過也可以創建可以回滾或者可更新的ResultSet,像下面這樣。
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
當生成ResultSet的Statement對象要關閉或者重新執行或是獲取下一個ResultSet的時候,ResultSet對象也會自動關閉。
可以通過ResultSet的getter方法,傳入列名或者從1開始的序號來獲取列數據。
ResultSet的不同類型
根據創建Statement時輸入參數的不同,會對應不同類型的ResultSet。如果你看下Connection的方法,你會發現createStatement和prepareStatement方法重載了,以支持不同的ResultSet和并發類型。
ResultSet對象有三種類型。
- ResultSet.TYPE_FORWARD_ONLY:這是默認的類型,它的游標只能往下移。
- ResultSet.TYPE_SCROLL_INSENSITIVE:游標可以上下移動,一旦它創建后,數據庫里的數據再發生修改,對它來說是透明的。
- ResultSet.TYPE_SCROLL_SENSITIVE:游標可以上下移動,如果生成后數據庫還發生了修改操作,它是能夠感知到的。
ResultSet有兩種并發類型。
- ResultSet.CONCUR_READ_ONLY:ResultSet是只讀的,這是默認類型。
- ResultSet.CONCUR_UPDATABLE:我們可以使用ResultSet的更新方法來更新里面的數據。
更多內容,歡迎關注微信公眾號:全菜工程師小輝~