JDBC:從原理到應用


一、是為何物

  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 驅動程序相應地分為四種類型。不同類型的驅動程序有着不一樣的特性和使用方法。

  1. JDBC-ODBC橋驅動程序(JDBC-ODBC Bridge Driver)
    此類驅動程序由JDBC-ODBC橋和一個ODBC驅動程序組成。其工作原理是,通過一段本地C代碼將JDBC調用轉化成ODBC調用。這一類型必須在本地計算機上先安裝好ODBC驅動程序,然后通過JDBC-ODBCBridge的轉換,將Java程序中使用的JDBCAPI訪問指令轉化成ODBCAPI指令,進而通過ODBC驅動程序調用本地數據庫驅動代碼完成對數據庫的訪問。
     
  2. 部分Java的本地JDBCAPI驅動程序
    JDBC API驅動程序(Anative API partly Java technology-enabled Driver)此類驅動程序也必須在本地計算機上先安裝好特定的驅動程序(類似ODBC),然后通過PartialJavaJDBCDriver的轉換,把Java程序中使用的JDBC API轉換成NativeAPI,進而存取數據庫。
  3. 純Java的數據庫中間件驅動程序(目前主流驅動)
    純Java的數據庫中間件驅動程序(Pure Java Driver for Database Middleware)使用這類驅動程序時,不需要在本地計算機上安裝任何附加軟件,但是必須在安裝數據庫管理系統的服務器端加裝中間件(Middleware),這個中間件負責所有存取數據庫時必要的轉換。其工作原理是:驅動程序將JDBC訪問轉換成與數據庫無關的標准網絡協議(通常是HTTP或HTTPS)送出,然后再由中間件服務器將其轉換成數據庫專用的訪問指令,完成對數據庫的操作。中間件服務器能支持對多種數據庫的訪問。
     
  4. 純Java的JDBC驅動程序
    純Java的JDBC驅動程序(Direct-to-DatabasePureJavaDriver)這類驅動程序是直接面向數據庫的純Java驅動程序,即所謂的"瘦"驅動程序。使用這類驅動程序時無需安裝任何附加的軟件(無論是本地計算機或是數據庫服務器端),所有存取數據庫的操作都直接由JDBC驅動程序來完成,此類驅動程序能將JDBC調用轉換成DBMS專用的網絡協議,能夠自動識別網絡協議下的特殊數據庫並能直接創建數據連接。

  第一類最節省投資。但執行效率比較低,不適合對大數據量存取的應用;第二種方式具有開放性,但不夠兼容使用較麻煩;

  第三類驅動程序是由純Java語言開發而成的,並且中間件也僅需要在服務器上安裝,不再需要客戶端的本機代碼,這類驅動程序的體積最小,效率較高,具有最大的靈活性。而且,此類驅動采用標准的網絡協議,可以被防火牆支持,是開發Applet程序理想的選擇(其實這些驅動是為Applet特別編寫的),是Internet應用理想的解決方案。另外,開發者還可以利用單一的驅動程序連接到多種數據庫。由於此種JDBC驅動程序提供了許多企業級的特征,因而非常適合用戶的特殊用途,如:SSL安全、分布式事務處理等。如果用戶未來擴張應用需要多個數據庫,則選擇這種方式是最理想的。由於它具有較好的性能和安全性,廣泛應用於基於Web的應用系統的開發。其不足之處是:需要在服務器端安裝中間件,這適當影響了驅動程序的效率。

  第四類驅動程序可能是最佳的JDBC驅動程序類型。這是一類新型的驅動程序,它由數據庫廠商提供,能夠實現對於本公司數據庫系統的最優化訪問。這種驅動程序的效率最高,擁有最佳的兼容性。然而,這種驅動由於使用DBMS專用的網絡協議,可能不被防火牆支持,在Internet中會存在潛在安全隱患,成為這類驅動最大的缺陷。其次,不同DBMS的驅動程序不同,在一個異構計算環境中,驅動程序的數量可能會比較多。

  由於我們主流選擇的JDBC驅動為第三類,下面給出第三類的JDBC驅動程序結構圖:

  

  從圖可見,JDBC驅動程序為兩層結構,它由驅動程序客戶端程序和驅動程序服務器端程序組成。客戶端直接與用戶交互,它為用戶提供符合JDBC規范的數據庫統一編程接口,客戶端將數據庫請求通過特定的網絡協議傳送給服務器。服務器充當中間件的角色,它負責接收和處理用戶的請求以及支持對多種數據庫的操作。JDBC驅動程序自身並不直接與數據庫交互,而是借助於其他已實現的數據庫驅動程序,成為“雇主”。可以想象,它“雇佣”的數據庫驅動程序越多,則可支持的數據庫就越多,“雇佣”的數據庫驅動程序越強大,則類型3JDBC驅動程序的功能也越強大。除此之外,“雇佣”的數據庫驅動程序的數量和成員可以動態改變,以滿足系統擴展的需求。第三類 JDBC驅動程序的這一特性正是它強大的根源所在。

二、動手測一測(以MySQL為例)

  了解了JDBC的大致原理,我們學會使用才是關鍵。

  1、環境

  既然要使用API,我們就需要導入JDBC運行環境所需要的jar包,而不同的數據庫jar包也是不同的,下面以MySQL數據庫為例,導入下列jar包(百度一堆或者去官網下載):

  

  2、一般步驟

  1. 注冊驅動
  2. 獲取連接對象(url、username、password)
  3. 編寫SQL語句
  4. 獲取語句執行對象
  5. (給占位符設置參數)
  6. 執行SQL語句
  7. 獲得執行后的結果集
  8. 處理結果集

  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)語句,比如 INSERTUPDATEDELETE 語句;或者是無返回內容的 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、自定義共享連接池

  一般步驟:

  1. 實現數據源DataSource,即實現Javax.sql.DataSource接口;由於只是簡單的演示,我們只實現其中的getConnection()方法即可。
  2. 創建一個LinkList容器。既然是“池子”,就需要保存東西,即存儲連接池對象,而連接池涉及移除/添加連接池對象,優先考慮使用LinkList來存儲。
  3. 使用靜態代碼塊初始化若干個連接池對象。由於只是測試,我們初始化3個就行。
  4. 實現getConnection()方法。注意,為了保證連接對象只提供給一個線程(一個用戶)使用,我們需要先將連接對象從池子中取出來
  5. 用完的連接對象不需要執行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

  1. 導包
    我們首先去下載一個C3P0壓縮包(自行百度)。然后解壓找到lib目錄,導入第一個包即可:
  2. 編寫配置文件: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>
  3. 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、准備工作

  1. 導包:既然是第三方工具,就需要導包,包括數據庫驅動包、DBUtils包和連接池包(這里我們使用C3P0連接池):
  2. 准備連接池:比如編寫c3p0工具類和配置文件。
  3. 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工具類

  1. QueryRunner類:
    new QueryRunner(DateSource ds):提供連接數據源(連接池)的方式。
    update(String sql,Object...params):執行更新操作。
    query(String sql,ResultSetHandler<T> rs,Object...paramas):執行查詢操作。
  2. ResultSetHandler接口:(重點常用的三個已標紅)


  3. 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     }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM