一、是為何物
1、概念
JDBC(Java Data Base Connectivity,java數據庫連接)是一種用於執行SQL語句的Java API,可以為多種關系數據庫提供統一訪問,它由一組用Java語言編寫的類和接口組成。JDBC提供了一種基准,據此可以構建更高級的工具和接口,使數據庫開發人員能夠編寫數據庫應用程序。簡言之,JDBC就是Java用於執行SQL語句實現數據庫操作的API。
如果不好理解我們可以先看一下驅動程序的概念:
驅動程序:指的是設備驅動程序(Device Driver),是一種可以使計算機和設備通信的特殊程序。相當於硬件的接口,操作系統只有通過這個接口,才能控制硬件設備的工作,假如某設備的驅動程序未能正確安裝,便不能正常工作。簡言之就是操作系統通過一段代碼來操作設備,而這段代碼就稱為該設備的驅動程序,是操作系統與硬件之間的橋梁。
同理,數據庫驅動程序就是提供用於操作數據庫的一段代碼。我們現在想要使用純Java來操作數據庫,肯定不能直接調用這段代碼,為了更便捷和靈活地使用高級語言直接對數據庫進行管理,JDBC因此而產生,JDBC是一套協議,是JAVA開發人員和數據庫廠商達成的協議,也就是由Sun定義一組接口,由數據庫廠商來實現,並規定了JAVA開發人員訪問數據庫所使用的方法的調用規范。
2、分類
據訪問數據庫的技術不同, JDBC 驅動程序相應地分為四種類型。不同類型的驅動程序有着不一樣的特性和使用方法。
- JDBC-ODBC橋驅動程序(JDBC-ODBC Bridge Driver)
此類驅動程序由JDBC-ODBC橋和一個ODBC驅動程序組成。其工作原理是,通過一段本地C代碼將JDBC調用轉化成ODBC調用。這一類型必須在本地計算機上先安裝好ODBC驅動程序,然后通過JDBC-ODBCBridge的轉換,將Java程序中使用的JDBCAPI訪問指令轉化成ODBCAPI指令,進而通過ODBC驅動程序調用本地數據庫驅動代碼完成對數據庫的訪問。 - 部分Java的本地JDBCAPI驅動程序
JDBC API驅動程序(Anative API partly Java technology-enabled Driver)此類驅動程序也必須在本地計算機上先安裝好特定的驅動程序(類似ODBC),然后通過PartialJavaJDBCDriver的轉換,把Java程序中使用的JDBC API轉換成NativeAPI,進而存取數據庫。
- 純Java的數據庫中間件驅動程序(目前主流驅動)
純Java的數據庫中間件驅動程序(Pure Java Driver for Database Middleware)使用這類驅動程序時,不需要在本地計算機上安裝任何附加軟件,但是必須在安裝數據庫管理系統的服務器端加裝中間件(Middleware),這個中間件負責所有存取數據庫時必要的轉換。其工作原理是:驅動程序將JDBC訪問轉換成與數據庫無關的標准網絡協議(通常是HTTP或HTTPS)送出,然后再由中間件服務器將其轉換成數據庫專用的訪問指令,完成對數據庫的操作。中間件服務器能支持對多種數據庫的訪問。
- 純Java的JDBC驅動程序
純Java的JDBC驅動程序(Direct-to-DatabasePureJavaDriver)這類驅動程序是直接面向數據庫的純Java驅動程序,即所謂的"瘦"驅動程序。使用這類驅動程序時無需安裝任何附加的軟件(無論是本地計算機或是數據庫服務器端),所有存取數據庫的操作都直接由JDBC驅動程序來完成,此類驅動程序能將JDBC調用轉換成DBMS專用的網絡協議,能夠自動識別網絡協議下的特殊數據庫並能直接創建數據連接。
第一類最節省投資。但執行效率比較低,不適合對大數據量存取的應用;第二種方式具有開放性,但不夠兼容使用較麻煩;
第三類驅動程序是由純Java語言開發而成的,並且中間件也僅需要在服務器上安裝,不再需要客戶端的本機代碼,這類驅動程序的體積最小,效率較高,具有最大的靈活性。而且,此類驅動采用標准的網絡協議,可以被防火牆支持,是開發Applet程序理想的選擇(其實這些驅動是為Applet特別編寫的),是Internet應用理想的解決方案。另外,開發者還可以利用單一的驅動程序連接到多種數據庫。由於此種JDBC驅動程序提供了許多企業級的特征,因而非常適合用戶的特殊用途,如:SSL安全、分布式事務處理等。如果用戶未來擴張應用需要多個數據庫,則選擇這種方式是最理想的。由於它具有較好的性能和安全性,廣泛應用於基於Web的應用系統的開發。其不足之處是:需要在服務器端安裝中間件,這適當影響了驅動程序的效率。
由於我們主流選擇的JDBC驅動為第三類,下面給出第三類的JDBC驅動程序結構圖:
從圖可見,JDBC驅動程序為兩層結構,它由驅動程序客戶端程序和驅動程序服務器端程序組成。客戶端直接與用戶交互,它為用戶提供符合JDBC規范的數據庫統一編程接口,客戶端將數據庫請求通過特定的網絡協議傳送給服務器。服務器充當中間件的角色,它負責接收和處理用戶的請求以及支持對多種數據庫的操作。JDBC驅動程序自身並不直接與數據庫交互,而是借助於其他已實現的數據庫驅動程序,成為“雇主”。可以想象,它“雇佣”的數據庫驅動程序越多,則可支持的數據庫就越多,“雇佣”的數據庫驅動程序越強大,則類型3JDBC驅動程序的功能也越強大。除此之外,“雇佣”的數據庫驅動程序的數量和成員可以動態改變,以滿足系統擴展的需求。第三類 JDBC驅動程序的這一特性正是它強大的根源所在。
二、動手測一測(以MySQL為例)
了解了JDBC的大致原理,我們學會使用才是關鍵。
1、環境
既然要使用API,我們就需要導入JDBC運行環境所需要的jar包,而不同的數據庫jar包也是不同的,下面以MySQL數據庫為例,導入下列jar包(百度一堆或者去官網下載):
2、一般步驟
- 注冊驅動
- 獲取連接對象(url、username、password)
- 編寫SQL語句
- 獲取語句執行對象
- (給占位符設置參數)
- 執行SQL語句
- 獲得執行后的結果集
- 處理結果集
3、Java代碼
下面看具體實現代碼:
-
1 public void JDBCDemo(String pname,int price) throws ClassNotFoundException, Exception{ 2 //1、注冊驅動 3 Class.forName("com.mysql.jdbc.Driver"); 4 //2、獲取連接對象 5 Connection conn = DriverManager.getConnection( 6 "jdbc:mysql://localhost:3306/web08?useUnicode=true&characterEncoding=utf-8&useSSL=false", 7 "root", "password"); 8 //sql語句 9 String sql = "select * from product where pname=? and price=?"; 10 //3、獲得sql語句執行對象 11 PreparedStatement ps = conn.prepareStatement(sql); 12 //4、設置參數給占位符 13 ps.setString(1, pname); 14 ps.setInt(2, price); 15 //5、執行並保存結果集 16 ResultSet rs = ps.executeQuery(); 17 //6、處理結果集 18 if(rs.next()){ 19 System.out.println(rs.getString(2)+":"+rs.getInt(3)); 20 }else 21 System.out.println("查詢失敗"); 22 if(rs!=null){ 23 rs.close(); 24 } 25 if(ps!=null){ 26 ps.close(); 27 } 28 if(conn!=null){ 29 conn.close(); 30 } 31 } 32 }
-
程序員在使用Java操作數據庫時只需要按照給定的規則進行編程即可操作數據庫。下面是常用api的詳細講解。
三、API詳解
1、注冊驅動程序
Class.forName(),是最常見的注冊JDBC驅動程序的方法,注冊某數據庫就將該數據庫驅動的名稱以字符串的形式作為參數:
例如我們需要注冊MySQL的數據庫驅動,則使用代碼:
-
1 Class.froName("com.mysql.jdbc.Driver");
另外還可以使用DriverManage的靜態方法:DriverManager.registerDriver()
來注冊驅動,參數為對應的數據庫驅動對象。
2、獲得連接對象Connection
可以使用 DriverManager.getConnection()
方法建立連接。根據傳入參數的不同,有三種重載的DriverManager.getConnection()
方法:
getConnection(String url)
getConnection(String url, Properties prop)
getConnection(String url, String user, String password)
這里每個格式都需要一個數據庫URL。 數據庫URL是指向數據庫的地址。制定數據庫URL是建立連接相關聯的大多數錯誤問題發生的地方。各數據庫對應的URL如下所示:
假設我們現在需要連接MySQL數據庫,格式為:jdbc:mysql://hostname:port/datebaseName。我們需要的信息是hostname主機名和端口號,一般默認為localHost:3306;還需要datebaseName數據庫名,假設為freeDB;當然還有URL格式未包含的也是必須的信息:連接數據庫的用戶名和密碼,假設為Leslie和030401。那么就有URL:
-
1 String url = "jdbc:mysql//localhost:3306/freeDB";
下面分別使用三種方法來實現:
- 使用一個URL作為參數的方式:需要將username+password以參數的形式放到URL中,但是每種數據庫的放置都不太相同
//連接mysql的純URL String url = "jdbc:mysql//localhost:3306/freeDB?username=leslie&password=030401";
Connection conn = DriverManager.getConnection(url,p);
//連接Oracle的純URL
String url = "jdbc:oracle:thin:leslie/030401@192.0.0.10:1521:freeDB";
Connection conn = DriverManager.getConnection(url); - 使用URL、properties作為參數的方式:即需要將username和password以鍵值對形式存放在properties對象中作為參數
1 //MySql 2 String url = "jdbc:mysql//localhost:3306/freeDB"; 3 Properties p = new Properties(); 4 p.put("username","leslie"); 5 p.put("password","030401"); 6 Connection conn = DriverManager.getConnection(url,p);
- 使用URL、username、password三個參數分開的方式(推薦)
1 String url = "jdbc:mysql//localhost:3306/freeDB"; 2 String username = "leslie"; 3 String password = "030401"; 4 Connection conn = DriverManager.getConnection(url,username,password);
3、獲得SQL語句執行對象
SQL語句的執行對象按理說有Statement和PreparedStatement兩個,但我們一般都不會去使用Statement,先看下兩者的基本描述:
- Statement 是 Java 執行數據庫操作的一個重要接口,用於在已經建立數據庫連接的基礎上,向數據庫發送要執行的SQL語句。Statement對象,用於執行不帶參數的簡單SQL語句,即靜態SQL語句。
- PreparedStatement 繼承於Statement。實例包含已編譯的 SQL 語句,這就是使語句“准備好”。包含於 PreparedStatement 對象中的 SQL 語句可具有一個或多個 IN 參數。IN參數的值在 SQL 語句創建時未被指定。相反的,該語句為每個 IN 參數保留一個問號(“?”)作為占位符。每個問號的值必須在該語句執行之前,通過適當的setXXX() 方法來提供。
簡言之,Statement執行靜態SQL語句,而它的子類PreparedStatement執行預編譯SQL,即可傳入參數。兩者相比之下,PreparedStatement有以下優勢:
- 預編譯處理,可動態執行SQL語句。很明顯,SQL語句的預編譯,使用占位符?去代替未知數據,因而一個句子可以執行多種不同的SQL,而Statement需要重新書寫SQL語句,笨重。
- 速度快,執行效率高。SQL語句會預編譯在數據庫系統中。執行計划同樣會被緩存起來,它允許數據庫做參數化查詢。使用預處理語句比普通的查詢更快,因為它做的工作更少(數據庫對SQL語句的分析,編譯,優化已經在第一次查詢前完成了)。我們要利用預編譯的特性,比如第一種查詢和第二種查詢,第二種才是預編譯形式,而第一種其實是恢復了父類Statement的做法:
1 //第一種方式,追加字符串:沒有進行預編譯,所以效率低 2 String loanType = getLoanType(); 3 PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=" + loanType); 4 5 //第二種方式,使用占位符,進行預編譯,效率高(推薦) 6 PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=?"); 7 prestmt.setString(1,loanType);
- 可防止SQL注入式攻擊。靜態SQL語句說到底還是字符串,所以存在拼字符串的而帶來的注入式SQL攻擊風險。比如某個網站的登錄驗證SQL查詢代碼為:
1 String sql = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';"
驗證需要用戶輸入用戶名和密碼,正確則執行查詢語句(登錄),但如果這樣輸入:
userName = "1' OR '1'='1"; passWord = "1' OR '1'='1";
那么執行語句就變成了:
1 String sql = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';"
這樣,where語句恆為真,就能實現無賬號登錄。此外便可能被惡意修改甚至刪除數據表。然而使用PreparedStatement的參數化的查詢可以阻止大部分的SQL注入。在使用參數化查詢的情況下,數據庫系統(eg:MySQL)不會將參數的內容視為SQL指令的一部分來處理,而是在數據庫完成SQL指令的編譯后,才套用參數運行,且占位符?不允許多值,只能填入一個值,因此就算參數中含有破壞性的指令,也不會被數據庫所運行。
說了那么多,PreparedStatement如何獲取呢?PreparedStatement是和連接對象Connection關聯的,所以我們需要使用Connection對象的PreparedStatement(sql)方法進行獲取:
-
String sql = "select * from user where name=? and ange=?";//預處理,需要我們先寫好sql語句 PreparedStatement ps = conn.preparedStatement(sql);//conn是連接對象,參數為sql語句
4、給占位符設置參數
根據需要的參數類型,使用setXXX()的方法即可。(注:從第一個問好起是從1開始編號)
-
1 ps.setString(1,"tom"); 2 ps.setInt(2,18);
5、執行SQL語句
sql語句有增刪查改等幾種類型,所以執行方法有以下三種:
execute():執行SQL語句,可以是任何種類的 SQL 語句。返回值是boolean類型。
executeQuery()
:至少SQL語句查詢,查詢結果返回為ResultSet
對象。executeUpdate()
:執行更新語句。該語句必須是一個 SQL 數據操作語言(Data Manipulation Language,DML)語句,比如INSERT
、UPDATE
或DELETE
語句;或者是無返回內容的 SQL 語句,比如 DDL 語句。返回值是int。
例如本例中的語句是查詢語句,所以執行代碼為:
-
1 ResultSet rs = ps.executeQuery();
6、處理結果或結果集
如果返回值是boolean或者int很好處理,但如果是查詢結果集ResultSet對象,一般使用while循環來處理:
ResultSet
對象具有指向其當前數據行的光標。最初,光標被置於第一行之前。next()
方法將光標移動到下一行;因為該方法在 ResultSet
對象沒有下一行時返回 false
,所以可以在 while
循環中使用它來迭代結果集。另外,可以使用ResultSet對象的getXXX(int columnIndex)獲得游標所在行指定列的值。原理如下圖所示:
所以,本例的結果集處理如下:
-
1 while(rs.next()){ 2 system.out.println(rs.getString(1)); 3 system.out.println(rs.getInt(2)); 4 }
7、關閉連接
在JDBC程序結束之后,顯式地需要關閉與數據庫的所有連接以結束每個數據庫會話。 但是,如果在編寫程序中忘記了關閉也沒有關系,Java的垃圾收集器將在清除過時的對象時也會關閉這些連接。
依靠垃圾收集,特別是數據庫編程,是一個非常差的編程實踐。所以應該要使用與連接對象關聯的close()
方法關閉連接。要確保連接已關閉,可以將關閉連接的代碼中編寫在“finally
”塊中。 一個finally
塊總是會被執行,不管是否發生異常。
-
conn.close();
四、工具類JDBCUtils的使用
在使用JDBC連接數據庫的過程中,我們發現大多數步驟都是一樣的,而我們平常使用數據庫的頻率如果很高所以我們需要對JDBC的通用代碼進行提煉,提高代碼復用率,提煉出來的工具類我們一般稱為JDBCUtils,工具類中包含了我們常用的很多方法,比如連接數據庫和斷開連接就是常用的方法,我們只要掌握了JDBC原理,就可以自己設計滿足需求工具類或參考以下工具類(后面我們會說到DBUtils工具類,這是Apache組織提供的JDBC工具類,比較全面,基本能夠滿足我們的需求,本文最后一節會進行詳解):
-
1 package cn.jdbc; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.sql.Connection; 6 import java.sql.DriverManager; 7 import java.sql.PreparedStatement; 8 import java.sql.ResultSet; 9 import java.sql.SQLException; 10 import java.util.Properties; 11 12 /** 13 * @author Fzz 14 * @date 2017年11月12日 15 * @Description TODO:提供獲取連接和釋放資源的方法 16 */ 17 public class jdbcUtil{ 18 /* 19 * 獲取Properties有兩種方法: 20 * 一是使用io流獲取文件內容, 21 * 二是使用jdk提供的專門獲取Properties配置文件的工具類:ResourceBundle 22 */ 23 private static String driver; 24 private static String url; 25 private static String username; 26 private static String password; 27 static{ 28 //1、通過本類獲取類加載器 29 ClassLoader classLoader = jdbcUtil_v3.class.getClassLoader(); 30 //2、使用類加載器獲得輸入流 31 InputStream is = classLoader.getResourceAsStream("jdbc.properties"); 32 //3、Properties工具類 33 Properties prop = new Properties(); 34 //4、加載流 35 try { 36 prop.load(is); 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } 40 //5、 41 driver = prop.getProperty("driver"); 42 url = prop.getProperty("url"); 43 username = prop.getProperty("username"); 44 password = prop.getProperty("password"); 45 } 46 /** 47 * @Title: getConnection 48 * @Description: TODO(獲取mySql連接 ) 49 * @return: Connection 50 */ 51 public static Connection getConnection(){ 52 Connection conn = null; 53 try { 54 //注冊驅動 55 Class.forName(driver); 56 //獲取連接 57 conn = DriverManager.getConnection(url,username,password); 58 } catch (Exception e) { 59 e.printStackTrace(); 60 } 61 return conn; 62 } 63 64 /** 65 * @Title: release 66 * @Description: TODO:釋放連接mySql的資源 67 * @return: void 68 */ 69 public static void release(Connection conn,PreparedStatement ps,ResultSet rs){ 70 try{ 71 if(rs != null){ 72 rs.close(); 73 } 74 if(ps != null){ 75 ps.close(); 76 } 77 if(conn != null){ 78 conn.close(); 79 } 80 }catch(SQLException e){ 81 e.printStackTrace(); 82 } 83 } 84 }
五、JDBC共享連接池的使用
對於頻繁使用數據庫或使用人數較多的項目,連接對象Connection的開開關關也是不好的吖,我們就可以設置JDBC連接池,連接池就相當於一個存放若干連接對象的池子,由於用戶人數多,每次訪問數據庫需要一個連接對象,那么就從池子里取出一個,用完放回即可,而不是銷毀。這樣,就可以實現連接池共享的目的,減少系統資源消耗。
Java提供了一個公共接口:Javax.sql.DataSource。此接口提供了 DataSource
對象所表示的物理數據源的連接。作為 DriverManager
工具的替代項,DataSource
對象是獲取連接的首選方法。
簡單來說,就是DateSource接口是Drivermanager的替代項,提供了getConnection()方法並生產標准的Connection對象,那么要實現連接池,就需要實現該接口和該方法。(所以我們常說的數據源也就是連接池)連接池處理自定義的方式,目前主要的連接池工具有C3P0(主流)、DBCP。
1、自定義共享連接池
一般步驟:
- 實現數據源DataSource,即實現Javax.sql.DataSource接口;由於只是簡單的演示,我們只實現其中的getConnection()方法即可。
- 創建一個LinkList容器。既然是“池子”,就需要保存東西,即存儲連接池對象,而連接池涉及移除/添加連接池對象,優先考慮使用LinkList來存儲。
- 使用靜態代碼塊初始化若干個連接池對象。由於只是測試,我們初始化3個就行。
- 實現getConnection()方法。注意,為了保證連接對象只提供給一個線程(一個用戶)使用,我們需要先將連接對象從池子中取出來。
- 用完的連接對象不需要執行close()而是放回池子去。
-
1 package cn.jdbcUtils; 2 3 import java.io.PrintWriter; 4 import java.sql.Connection; 5 import java.sql.SQLException; 6 import java.sql.SQLFeatureNotSupportedException; 7 import java.util.LinkedList; 8 import java.util.logging.Logger; 9 10 import javax.sql.DataSource; 11 12 import cn.day09.jdbc.jdbcUtil; 13 14 /** 15 * @author Fzz 16 * @date 2017年11月12日 17 * @Description TODO:自定義JDBC連接池 18 */ 19 20 public class MyDataSource implements DataSource{//[1]實現接口 21 //[2]創建一個容器存儲連接池里的Connection對象。 22 private static LinkedList<Connection> pool = new LinkedList<Connection>(); 23 24 //[3]初始化3個Connection對象放進池子。 25 static{ 26 Connection conn = null; 27 for (int i = 0; i < 3; i++) { 28 conn = jdbcUtil.getConnection();//這里我們使用上面創建的jdbcUtils來獲取連接 29 pool.add(conn); 30 } 31 } 32 33 @Override 34 /** 35 * [4]從池子里取連接對象 36 */ 37 public Connection getConnection() throws SQLException { 38 //使用前先判斷連接池是否有連接對象,沒有則添加 39 Connection conn = null; 40 if(pool.size() == 0){ 41 for (int i = 0; i < 3; i++) { 42 conn = jdbcUtil.getConnection(); 43 pool.add(conn); 44 } 45 } 46 conn = pool.removeFirst();//取出來 47 return conn; 48 } 49 50 /** 51 * [5]用完歸還連接到連接池 52 */ 53 public boolean returnConnToPool(Connection conn){ 54 return pool.add(conn); 55 } 56 57 //下面是未實現的方法。 58 @Override 59 public PrintWriter getLogWriter() throws SQLException { 60 // TODO Auto-generated method stub 61 return null; 62 } 63 @Override 64 public void setLogWriter(PrintWriter out) throws SQLException { 65 // TODO Auto-generated method stub 66 67 } 68 @Override 69 public void setLoginTimeout(int seconds) throws SQLException { 70 // TODO Auto-generated method stub 71 72 } 73 @Override 74 public int getLoginTimeout() throws SQLException { 75 // TODO Auto-generated method stub 76 return 0; 77 } 78 @Override 79 public Logger getParentLogger() throws SQLFeatureNotSupportedException { 80 // TODO Auto-generated method stub 81 return null; 82 } 83 @Override 84 public <T> T unwrap(Class<T> iface) throws SQLException { 85 // TODO Auto-generated method stub 86 return null; 87 } 88 @Override 89 public boolean isWrapperFor(Class<?> iface) throws SQLException { 90 // TODO Auto-generated method stub 91 return false; 92 } 93 @Override 94 public Connection getConnection(String username, String password) throws SQLException { 95 // TODO Auto-generated method stub 96 return null; 97 } 98 }
【擴展:方法的增強】
自定義共享連接池到了這里基本已經完成,但美中不足的是:之前我們使用連接對象是這樣的,用getConnection()方法獲取,用完再用close()關閉。現在我們通過共享連接池來使用連接對象,是用getConnection()取出連接對象,使用returnConnToPool()歸還連接對象。而美中不足的就是,我們希望通過共享連接池使用連接對象是仍然使用getConnection()和close()這兩個方法,以保持前后一致的美感(呵呵),但我們是實現DataSource接口,接口中沒有close()方法。可能你會說這是多余,就直接使用returnConnToPool()不挺好的嗎,還能加深理解。
其實,這里我只是作為另一個知識點的引申——方法的增強,以此找了個小小的借口。(碧池)
目前有四種方法可以進行某個方法的增強,但各有優缺點:
- 使用繼承增強方法。
使用前提是需要實現繼承關系,子類通過繼承父類方法,進而改進方法。 - 使用裝飾者設計模式增強方法。
裝飾者設計模式是專門用來增強方法的設計模式,使用前提是需要裝飾者實現被裝飾對象(即被增強對象)相同接口,以實現交互。裝飾模式是在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。裝飾者模式比繼承靈活,但程序也相對復雜。 - 使用動態代理增強方法。(難點)
動態代理與裝飾者模式類似,且需要使用反射技術。 - 使用字節碼增強方法。(難點)
Java字節碼增強指的是在Java字節碼生成之后,對其進行修改,增強其功能,這種方式相當於對應用程序的二進制文件進行修改。Java字節碼增強的應用場景主要是減少冗余代碼,對開發人員屏蔽底層的實現細節。字節碼增強技術主要有兩種實現機制:一種是通過創建原始類的一個子類;另一種是直接去修改原先的class字節碼。
目前有cglib、Javassist等框架提供使用。
上面只是簡單的介紹,有意者可以自行搜索。此處不是本文重點故不再贅述,其實是筆者自己也沒學。(只想對筆者說一句:“多撈啊”)
2、C3P0連接池的使用
C3P0是開源的共享連接池,比如Sring、Hibernate等開源項目都有使用C3P0。C3P0是第三方工具,除了需要導出相應jar包,還需要編寫配置文件 c3p0-config.xml。
- 導包
我們首先去下載一個C3P0壓縮包(自行百度)。然后解壓找到lib目錄,導入第一個包即可: - 編寫配置文件:c3p0-config.xml
其實不一定非要配置文件,但相關設置比較麻煩建議編寫。在壓縮包的doc目錄下有 index.html是c3p0的幫助文檔(英文版):
幫助文檔中就有配置文件相關格式的說明,這里總結如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <c3p0-config> 3 4 <!-- 默認配置1--> 5 <default-config> 6 <連接數據庫的一些基本參數> 7 <property name="driverClass">com.mysql.jdbc.Driver</property> 8 <property name="jdbcUrl">jdbc:mysql:///FreeDB</property> 9 <property name="user">leslie</property> 10 <property name="password">030401</property> 11 <property name="initialPoolSize">5</property> 12 <property name="maxPoolSize">20</property> 13 </default-config> 14 15 <!-- 配置2命名 --> 16 <named-config name="mysql_web09"> 17 <property name="driverClass">數據庫驅動</property> 18 <property name="jdbcUrl">數據庫url</property> 19 <property name="user">用戶名</property> 20 <property name="password">密碼</property> 21 <property name="initialPoolSize">最小連接數</property> 22 <property name="maxPoolSize">最大連接數</property> 23 </named-config> 24 25 </c3p0-config>
- C3P0Utils
c3p0提供了一個工具類可以方便我們的使用,免去了多余類和方法的學習和直接使用:
1 package cn.c3p0Utils; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import com.mchange.v2.c3p0.ComboPooledDataSource; 6 7 /** 8 * C3P0工具類 9 * @author Fzz 10 * @date 2017年11月12日12 */ 13 public class C3P0Utils { 14 //創建c3p0的連接池對象(注:ComboPooledDataSource空參數時使用默認配置,否則傳入需要使用的配置名字即可) 15 private static ComboPooledDataSource datasource = new ComboPooledDataSource(); 16 /** 17 * 返回DataSource 18 * @Title: getDataSource 19 * @Description: TODO:獲得連接池對象 20 * @return: ComboPooledDataSource 21 */ 22 public static ComboPooledDataSource getDataSource(){ 23 return datasource;//將連接池對象返回即可 24 } 25 /** 26 * 返回Connection 27 * @Title: getConnection 28 * @Description: TODO:獲得連接對象(從池子中取) 29 * @return: Connection 30 */ 31 public static Connection getConnection(){ 32 try { 33 return datasource.getConnection(); 34 } catch (SQLException e) { 35 throw new RuntimeException(e); 36 } 37 } 38 }
注:這里C3P0Utils提供沒有進行連接對象的歸還方法,這是因為一我們在使用完連接對象conn后直接使用conn.close()即可歸還。(c3p0使用代理已經增強了close()方法不是關閉是歸還連接池,和上文拓展說的一個意思),二是后面我們要使用DBUtils工具類進行數據庫操作,而DBUtils底層會自動維護連接對象,固此處不再提供歸還方法。
六、使用DBUtils工具類進行數據庫測CRUD(增查改刪)
上文我們也說到JDBC工具類對提高代碼復用率的重要性,DBUtils是Apache大佬提供的一個對JDBC進行簡單封裝的開源工具類庫,使用它能夠簡化JDBC應用程序的開發,同時又不會影響程序的性能。
1、准備工作
- 導包:既然是第三方工具,就需要導包,包括數據庫驅動包、DBUtils包和連接池包(這里我們使用C3P0連接池):
- 准備連接池:比如編寫c3p0工具類和配置文件。
- JavaBean類的編寫。JavaBean是一種JAVA語言規范寫成可重用組件的類。寫成JavaBean的類,類必須是具體的和公共的,並且具有無參數的構造器。JavaBean 通過提供符合一致性設計模式的公共方法將內部域暴露成員屬性,set和get方法獲取。眾所周知,屬性名稱符合這種模式,其他Java 類可以通過自省機制(反射機制)發現和操作這些JavaBean 的屬性。比如我的數據庫中有一個category表,那么我就可以設計一個JavaBean類。
1 package cn.domain; 2 3 public class category { 4 private String cid; 5 private String cname; 6 public category(){ 7 8 } 9 public String getCid() { 10 return cid; 11 } 12 public void setCid(String cid) { 13 this.cid = cid; 14 } 15 public String getCname() { 16 return cname; 17 } 18 public void setCname(String cname) { 19 this.cname = cname; 20 } 21 22 }
2、三個核心類
在使用之前我們先學習DBUtils的三個核心類:提供SQL語句操作API的QueryRunner類、用於執行select語句后進行結果集封裝的ResultSetHandler接口、提供資源關閉、處理事務等方法的DbUtils工具類。
- QueryRunner類:
new QueryRunner(DateSource ds):提供連接數據源(連接池)的方式。
update(String sql,Object...params):執行更新操作。
query(String sql,ResultSetHandler<T> rs,Object...paramas):執行查詢操作。 - ResultSetHandler接口:(重點常用的三個已標紅)
- DbUtils工具類:
closeQuietly(Connection conn):關閉連接,如果有異常try后不拋。
commitAndCloseQuietly(Connection conn):提交事務並關閉連接。
rollbackAndCloseQuietly(Connection conn):回滾事務並關閉連接。
3、實現CRUD操作
這里舉一個例子作為參考,其他步驟基本一致:
1 //使用DBUtils查詢數據庫 2 @Test 3 public void queryAll(){ 4 try { 5 //1、使用QueryRunner獲得數據源(連接池) 6 QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); 7 //2、編寫sql語句 8 String sql = "select * from category"; 9 //3、執行sql語句 10 List<category> query = qr.query(sql,new BeanListHandler<category>(category.class)); 11 //4、處理結果 12 for(category c:query){ 13 System.out.println(c.getCid()+":"+c.getCname()); 14 } 15 } catch (SQLException e) { 16 throw new RuntimeException(e); 17 } 18 }