首頁>技術>

背景

前兩天一個小夥伴面試的時候,被問JDBC底層是如何連線資料庫的?

他頓時一臉懵逼,因為大部分人只知道JDBC的幾個步驟,至於底層到底是怎麼連線資料庫的,還真不知道。

由於小夥伴是面試高階開發,問這種問題倒也不能說面試官過分,如果是初級或者中級,那問著問題就確實有些過分了。

但是如果你在初級或者中級的階段,就知道了答案,豈不是爽歪歪麼?

估計大部分人都不知道這個問題該怎麼回答,稍微發散一下思維,倒是可以猜測一下,今天我們就來搞清楚JDBC底層到底是如何連線資料庫的。往後別再猜了。

反過來,如果面試官問你JDBC的時候,你能知道底層是怎麼連線資料庫的,估計,很多相對較水的面試官也會一臉懵逼。

何為 JDBC ?

JDBC(Java DataBase Connectivity)是Java和資料庫之間的一個橋樑,是一個「規範」而不是一個實現,能夠執行SQL語句。JDBC由一組用Java語言編寫的類和介面組成。各種不同型別的資料庫都有相應的實現,注意:本文中的程式碼都是針對MySQL資料庫實現的。

JDBC 架構

分為雙層架構和三層架構。

雙層

作用:此架構中,Java Applet 或應用直接訪問資料來源。

條件:要求 Driver 能與訪問的資料庫互動。

機制:使用者命令傳給資料庫或其他資料來源,隨之結果被返回。

部署:資料來源可以在另一臺機器上,使用者透過網路連線,稱為 C/S配置(可以是內聯網或網際網路)。

三層

側架構特殊之處在於,引入中間層服務。

流程:命令和結構都會經過該層。

吸引:可以增加企業資料的訪問控制,以及多種型別的更新;另外,也可簡化應用的部署,並在多數情況下有效能優勢。

歷史趨勢:以往,因效能問題,中間層都用 C 或 C++ 編寫,隨著最佳化編譯器(將 Java 位元組碼 轉為 高效的 特定機器碼)和技術的發展,如EJB,Java 開始用於中間層的開發這也讓 Java 的優勢突顯出現出來,使用 Java 作為伺服器程式碼語言,JDBC隨之被重視。

入門案例

下面給出一個JDBC入門級案例:

public class JdbcDemo {    public static final String URL = "jdbc:mysql://localhost:3306/mblog";    public static final String USER = "root";    public static final String PASSWORD = "123456";    public static void main(String[] args) throws Exception {         Class.forName("com.mysql.jdbc.Driver");         Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);         Statement stmt = conn.createStatement();         ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1");         while(rs.next()){            System.out.println("name: "+rs.getString("name")+" 年齡:"+rs.getInt("age"));        }    }}
JDBC 步驟

資料庫驅動:

Class.forName("com.mysql.jdbc.Driver"); 

獲取連線:

Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); 

建立Statement或者PreparedStatement物件:

Statement stmt = conn.createStatement(); 

執行sql資料庫查詢:

ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1"); 

解析結果集:

System.out.println("name: "+rs.getString("name")+" 年齡:"+rs.getInt("age"));

最後就是各種資源的關閉。

資料庫驅動

載入MySql的驅動類 :

Class.forName("com.mysql.jdbc.Driver"); 

我們安裝好資料庫之後,我們的應用程式也是不能直接使用資料庫的,必須要透過相應的資料庫驅動程式,透過驅動程式去和資料庫打交道。其實也就是資料庫廠商的JDBC介面實現,即對Connection等介面的實現類的jar檔案。

Driver介面

java.sql.Driver此介面是提供給資料庫廠商實現的。比如說MySQL的,需要依賴對應的jar包。

<dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>8.0.16</version></dependency>

MySQL資料庫對應的實現驅動實現類:

package com.mysql.cj.jdbc;import java.sql.SQLException; public class Driver extends NonRegisteringDriver implements java.sql.Driver {    static {        try {            //註冊驅動            java.sql.DriverManager.registerDriver(new Driver());        } catch (SQLException E) {            throw new RuntimeException("Can't register driver!");        }    }     public Driver() throws SQLException {     }}

DriverManager是rt.jar包下的類,(rt=runtime),把我們需要驅動類註冊進去。

//DriverManager類中的方法public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da)    throws SQLException {     /* Register the driver if it has not already been added to our list */     if(driver != null) {          registeredDrivers.addIfAbsent(new DriverInfo(driver, da));      } else {          // This is for compatibility with the original DriverManager          throw new NullPointerException();      }      println("registerDriver: " + driver);}

相應裝載Oracle驅動:

Class.forName("oracle.jdbc.driver.OracleDriver"); 

Sql Server驅動:

Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
獲取連結

給我們看起來就這一行程式碼:

Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);

下面我們進行深入聊聊這行程式碼,到底底層是怎麼連線資料庫的?

getConnection方法三個引數:連結地址,使用者名稱和密碼。

public static Connection getConnection(String url,     String user, String password) throws SQLException {     java.util.Properties info = new java.util.Properties();     if (user != null) {         info.put("user", user);     }     if (password != null) {         info.put("password", password);     }   return (getConnection(url, info, Reflection.getCallerClass())); }

建立一個Properties物件,Properties是HashTable的子類。

public class Properties extends Hashtable<Object,Object> {    //.....}

再看getConnection方法:

//  Worker method called by the public getConnection() methods.private static Connection getConnection(        String url, java.util.Properties info, Class<?> caller) throws SQLException {  ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;  SQLException reason = null;  //遍歷氣門註冊的資料庫驅動  for(DriverInfo aDriver : registeredDrivers) {             try {                 //獲取連線                Connection con = aDriver.driver.connect(url, info);                if (con != null) {                    // Success!                    println("getConnection returning " + aDriver.driver.getClass().getName());                    return (con);                }            } catch (SQLException ex) {                if (reason == null) {                    reason = ex;                 }            }      }}

這段程式碼的關鍵是這一句程式碼:

Connection con = aDriver.driver.connect(url, info);

connet()方法是每個資料庫驅動自己的實現的。

package com.mysql.cj.jdbc;public class NonRegisteringDriver implements java.sql.Driver {     @Override    public java.sql.Connection connect(String url, Properties info) throws SQLException {         //部分無關鍵要的程式碼省略        //下面是重點        ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);        switch (conStr.getType()) {                //SINGLE_CONNECTION("jdbc:mysql:", HostsCardinality.SINGLE), //                case SINGLE_CONNECTION:                    return com.mysql.cj.jdbc.ConnectionImpl.getInstance(conStr.getMainHost());                case LOADBALANCE_CONNECTION:                    return LoadBalancedConnectionProxy.createProxyInstance((LoadbalanceConnectionUrl) conStr);                case FAILOVER_CONNECTION:                    return FailoverConnectionProxy.createProxyInstance(conStr);                case REPLICATION_CONNECTION:                    return ReplicationConnectionProxy.createProxyInstance((ReplicationConnectionUrl) conStr);                default:                    return null;        }     }}    

ConnectionUrl從這個類名應該能猜到還不到真正連線的,只是建立一個連線Url相關資訊封裝。

public abstract class ConnectionUrl implements DatabaseUrlContainer {    private static final String DEFAULT_HOST = "localhost";    private static final int DEFAULT_PORT = 3306;    //...}    

熟悉的身影,MySQL資料庫預設埠。我們繼續看下一行重要的程式碼:

ConnectionImpl.getInstance(conStr.getMainHost());

這裡就是獲取一個例項,不出意外,連線就在這裡面產生的。繼續:

//ConnectionImplpublic static JdbcConnection getInstance(HostInfo hostInfo) throws SQLException {     return new ConnectionImpl(hostInfo);}

ConnectionImpl構造方法裡有呼叫createNewIO方法:

    @Override    public void createNewIO(boolean isForReconnect) {        synchronized (getConnectionMutex()) {              try {                if (!this.autoReconnect.getValue()) {                    connectOneTryOnly(isForReconnect);                    return;                }                connectWithRetries(isForReconnect);            } catch (SQLException ex) {             }        }    }private void connectOneTryOnly(boolean isForReconnect) throws SQLException {        Exception connectionNotEstablishedBecause = null;             JdbcConnection c = getProxy();            //又看到熟悉的connet方法,            this.session.connect(this.origHostInfo, this.user, this.password, this.database, DriverManager.getLoginTimeout() * 1000, c);             this.session.setQueryInterceptors(this.queryInterceptors);      }

其中,這裡的session是NativeSession。

public void connect(HostInfo hi, String user, String password, String database, int loginTimeout, TransactionEventHandler transactionManager)            throws IOException {      SocketConnection socketConnection = new NativeSocketConnection();    socketConnection.connect(this.hostInfo.getHost(), this.hostInfo.getPort(), this.propertySet, getExceptionInterceptor(), this.log, loginTimeout);     this.protocol.connect(user, password, database);                     this.protocol.getServerSession().setErrorMessageEncoding(this.protocol.getAuthenticationProvider().getEncodingForHandshake()); }

在這個方法裡,我們看到了Socket的命名開頭的類,哈哈,是不是就是使用Socket進行通訊的呢?

精彩繼續:

 socketConnection.connect(this.hostInfo.getHost(), this.hostInfo.getPort(), ...); 

來到NativeSocketConnection類中方法:

//com.mysql.cj.protocol.a.NativeSocketConnection@Overridepublic void connect(String hostName, int portNumber, PropertySet propSet, ExceptionInterceptor excInterceptor, Log log, int loginTimeout) {    this.mysqlSocket = this.socketFactory.connect(this.host, this.port, propSet, loginTimeout);  //... }            

這裡的socketFactory是StandardSocketFactory。所以也就是呼叫的是StandardSocketFactory的connect方法:

//StandardSocketFactorypublic <T extends Closeable> T connect(String hostname, int portNumber, PropertySet pset, int loginTimeout) throws IOException {    this.rawSocket = createSocket(pset);    this.rawSocket.connect(sockAddr, getRealTimeout(connectTimeout));}   protected Socket createSocket(PropertySet props) {     return new Socket();}

這裡就算到底了,說白JDBC的底層就是使用「Socket」進行連線資料庫的。

常用方法

方法描述createStatement()建立向資料庫傳送sql的statement物件。prepareStatement(sql)建立向資料庫傳送預編譯sql的PrepareSatement物件。prepareCall(sql)建立執行儲存過程的callableStatement物件。setAutoCommit(boolean autoCommit)設定事務是否自動提交。commit()在連結上提交事務。rollback()在此連結上回滾事務。

獲取Statement三種類型

要執行SQL語句,必須獲得java.sql.Statement例項,Statement例項分為以下3 種類型:

執行靜態SQL語句。通常透過Statement例項實現。執行動態SQL語句。通常透過PreparedStatement例項實現。執行資料庫儲存過程。通常透過CallableStatement例項實現。具體獲取方式
Statement stmt = con.createStatement() ;   PreparedStatement pstmt = con.prepareStatement(sql) ;   CallableStatement cstmt =  con.prepareCall("{CALL demoSp(? , ?)}") ;   
常用方法

方法含義executeQuery(String sql)用於向資料傳送查詢語句。executeUpdate(String sql)用於向資料庫傳送insert、update或delete語句execute(String sql)用於向資料庫傳送任意sql語句addBatch(String sql)把多條sql語句放到一個批處理中。executeBatch()向資料庫傳送一批sql語句執行。

Statement和PreparedStatement的異同及優缺點

同:兩者都是用來執SQL語句的

異:PreparedStatement需要根據SQL語句來建立,它能夠透過設定引數,指定相應的值,不是像Statement那樣使用字串拼接的方式。

PreparedStatement的優點:

1、其使用引數設定,可讀性好,不易記錯。在statement中使用字串拼接,可讀性和維護性比較差。

2、其具有預編譯機制,效能比statement更快。

3、其能夠有效防止SQL注入攻擊。

不同點:

1、execute可以執行查詢語句,然後透過getResult把結果取出來。executeUpdate不能執行查詢語句。

2、execute返回Boolean型別,true表示執行的是查詢語句,false表示執行的insert、delete、update等。executeUpdate的返回值是int,表示有多少條資料受到了影響。

13
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Python影象處理實現兩幅影象合成一幅影象的方法