日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

背景

前兩天一個小伙伴面試的時候,被問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文件。

面試被問:JDBC底層是如何連接數據庫的?

 

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());

這里就是獲取一個實例,不出意外,連接就在這里面產生的。繼續:

//ConnectionImpl
public 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
@Override
public 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方法:

//StandardSocketFactory
public <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注入攻擊。

execute和executeUpdate的區別

相同點:二者都能夠執行增加、刪除、修改等操作。

不同點:

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

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

分享到:
標簽:JDBC
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定