Java數據庫持久層框架基礎:jdbc原理和使用綜述

前言

    目前java web項目流行的各種數據庫持久層框架,如mybatis以及mybatis plus等mybatis各種變體、ORM框架包括Hibernate、spring data JPA等,底層的數據庫訪問都是依託於jdbc。雖然成熟項目中使用上述的持久層框架,並不能感知到jdbc的存在,但實際上很多項目中數據庫訪問的問題,本身都能夠從jdbc的基礎原理中找到依據或答案。

jdbc基礎架構

Java數據庫持久層框架基礎:jdbc原理和使用綜述-jdbc 架構.png     jdbc最初的設計是面向Java應用層提供數據庫查詢的API(如上圖所示),簡化應用層的數據查詢流程。雖然後續由於jdbc不夠簡便,現在直接和應用層交互的是數據持久層的框架,但底層依然是依賴jdbc提供的API接口。

    在JDBC層,使用DriverManager來獲取驅動、創建和管理連接Connection,每一個Connection對應着一個數據庫連接,通過這個連接創建會話Statement,執行sql語句,獲得數據查詢返回的結果集ResultSet。在驅動層,jdbc可以使用數據庫廠商提供給JDBC的驅動(JDBC Driver)訪問數據庫。對於windows下常用的ODBC,可以通過JDBC-ODBC bridge模式使用ODBC Driver。

jbdc的使用

    在介紹更深層次的原理之前,首先需要對jdbc的使用有感性的認識。以下代碼展示了jdbc使用的簡要流程。

/**************1. 加載驅動************/
try{
    Class.forName("com.mysql.cj.jdbc.Driver") ;
}catch(ClassNotFoundException e){
    System.out.println("加載驅動失敗!");
    e.printStackTrace() ;
    return null;
}
/************************************/

Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try{
/********2. 獲取連接Connection ********/
     //訪問本地mysql的test庫
     String url = "jdbc:mysql://localhost:3306/test" ;
     String username = "root" ;
     String password = "root" ;
     connection = DriverManager.getConnection(url , username , password ) ;
/************************************/

//3.獲取會話Statement
     statement = connection.createStatement() ;
//4.執行查詢、獲取結果集ResultSet
     resultSet = statement.executeQuery("SELECT * FROM movie") ;
//5.打印結果
    while(resultSet.next()){
        String name = resultSet.getString("name") ;
        String year = resultSet.getString(1) ;
        System.out.println("電影:"+ name +"上映於"+year+"年.");
    }
}catch(SQLException se){
    System.out.println("數據庫訪問失敗!");
    se.printStackTrace() ;
} finally {

/*******6.依次關閉結果集、會話和連接*******/
    if(resultSet !=null){
        try {
            resultSet.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if(statement !=null){
        try {
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if(connection !=null){
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
/**************************************/
}
return null;

jdbc的主要接口

Java數據庫持久層框架基礎:jdbc原理和使用綜述-jdbc接口.png

驅動

    在獲取連接之前,要保證對應數據的驅動已經被加載。驅動的正確選用由DriveManager自動管理,用戶不需要關心。(com.mysql.cj.jdbc.Driver是Mysql的最新jdbc驅動,之前使用的驅動com.mysql.jdbc.Driver現在官方已經不建議使用。)

連接

    Jdbc使用DriverManager管理驅動、獲取連接。獲取連接的必要條件是數據庫路徑、用戶名和密碼。數據庫路徑的書寫分爲兩類:

  • JDBC源:jdbc:子協議:<主機名:[端口號]/數據源名><參數>,如上例的jdbc:mysql://localhost:3306/test,訪問是數據庫是本地mysql的test庫。
  • ODBC源:使用ODBC橋,jdbc:odbc:<主機名:[端口號]/數據源名><參數>

    DriverManager.getConnection(url , username , password )獲取連接,即創建的一個連接只能對應一個數據源,如果想要訪問多個數據庫,需要創建多個連接。

連接池

    需要注意的是jdbc方式創建連接的方式比較慢。如果對於大量數據的數據庫訪問,單連接查詢的效率受限,但即時創建多個連接併發,一方面存在時間問題,另一方面面臨創建後如何管理的問題(使用、回收銷燬),所以現在普遍的解決方案是使用連接池。目前使用常用的連接池有hikariCP、druid、dbcp、c3p0等。
    根據上文對連接池的使用目的的解釋,可知每一個連接池實例中所有連接都對應相同的數據源連接配置,這意味着如果需要增加新的數據庫訪問,就需要新建相應配置的連接池。如果是spring mvc或者更成熟的spring boot的項目,每一個DataSource包含的也就是數據源、用戶名、密碼、數據訪問設置相關的配置,每一個DataSource也都對應一個連接池實例。
    連接池會有幾個比較重要的配置。首先是連接數,包括最大連接數、最大空閒數。最大連接數定義了連接池中最多可以持有多少個連接,最大空閒數定義了連接池中最多可以有多少個空閒連接,這兩個參數主要影響了數據庫訪問的併發量。其次是時間值,主要包括超時時間和最長空閒時間。超時時間用來指定數據庫訪問多久未返回則報超時,最長空閒時間用來定義連接最大空閒多久則會被連接池回收。
    這裏有一個著名的坑:mysql數據庫本身能夠設置連接超時時間wait_timeout,如果連接池的連接的最大空閒時間大於這個閾值,則連接可能已經被因數據庫斷掉而失效了,但該連接在池中還能夠被使用。此時使用此連接時就會報錯。對於這種情況有三種解決方式:

  1. 設置最大空閒時間小於數據庫的連接超時時間,在數據庫連接超時之前該連接就能夠被連接池回收;
  2. 使用空閒連接檢測,有些連接池會有testOnBorrow或者testOnReturn配置,在使用或者放回連接時檢測連接時候有效。
  3. keep alive機制,可以設置一個無意義的查詢,比如SELECT 1,每過一段時間執行查詢保持連接有效,這樣做會損失一定性能。

會話Statement

    jdbc使用Statement來執行sql語句,可以使用三種類型的Statement,對應三種類型的sql執行機制。

  1. 普通Statement:即上文例子中由connection.createStatement()創建的Statement,用來執行普通靜態sql。
  2. PreparedStatement:由connection.prepareStatement(sql)創建。
  • PreparedStatement和普通Statement的不同之處在於,創建Statement需要傳入sql語句,sql語句支持傳入佔位符,類似於 WHERE id=?,後續需要設置佔位符的值,然後執行。
  • 這樣做的好處是,在創建Statement後,數據庫系統會對sql語句進行預編譯處理(如果JDBC驅動支持的話),預處理語句將被預先編譯好,這條預編譯的sql查詢語句能在將來的查詢中重用,因此比PreparedStatement要比Statement對象生成的查詢速度更快。
  1. CallableStatement:由connection.prepareCall("{call show_movies()}")創建。
  • show_movies()是自主定義的數據庫的存儲過程函數,存儲過程可以一次性執行多條SQL語句的目的。在使用之前,首先需要在數據庫中創建一個存儲過程,比如使用數據庫命令行創建show_usertbl():“create procedure sp_movies () select * from movie;”

結果集ResultSet

    對於更新、插入和查詢的語句執行結果是不一樣的,比如普通Statement的更新語句的執行int executeUpdate(String sqlString),返回更新條目的計數,插入語句返回的結果爲布爾值是否成功。返回結果中比較複雜的是結果集,可以使用上例中resultSet.next()對結果集中的每條記錄進行遍歷,使用字段名或者字段索引index獲取字段值。

最後,在流程上,當完成所有查詢或者查詢中遭遇異常,需要依次關閉結果集、會話和連接,回收資源。正如上例代碼第6步所做的那樣,需要使用接口的close方法,完成關閉和資源回收。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章