關注「Java後端技術全棧」java
回覆「面試」獲取全套面試資料mysql
前兩天一個小夥伴面試的時候,被問JDBC底層是如何鏈接數據庫的?面試
他頓時一臉懵逼,由於大部分人只知道JDBC的幾個步驟,至於底層究竟是怎麼鏈接數據庫的,還真不知道。sql
因爲小夥伴是面試高級開發,問這種問題倒也不能說面試官過度,若是是初級或者中級,那問着問題就確實有些過度了。數據庫
可是若是你在初級或者中級的階段,就知道了答案,豈不是爽歪歪麼?後端
估計大部分人都不知道這個問題該怎麼回答,稍微發散一下思惟,卻是能夠猜想一下,今天咱們就來搞清楚JDBC底層究竟是如何鏈接數據庫的。日後別再猜了。服務器
反過來,若是面試官問你JDBC的時候,你能知道底層是怎麼鏈接數據庫的,估計,不少相對較水的面試官也會一臉懵逼。網絡
JDBC(Java DataBase Connectivity)是Java和數據庫之間的一個橋樑,是一個「規範」而不是一個實現,可以執行SQL語句。JDBC由一組用Java語言編寫的類和接口組成。各類不一樣類型的數據庫都有相應的實現,注意:本文中的代碼都是針對MySQL數據庫實現的。session
分爲雙層架構和三層架構。架構
做用:此架構中,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")); } } }
數據庫驅動:
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文件。
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()
在此連接上回滾事務。
要執行SQL語句,必須得到java.sql.Statement實例,Statement實例分爲如下3 種類型:
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語句執行。
同:二者都是用來執SQL語句的
異:PreparedStatement須要根據SQL語句來建立,它可以經過設置參數,指定相應的值,不是像Statement那樣使用字符串拼接的方式。
PreparedStatement的優勢:
一、其使用參數設置,可讀性好,不易記錯。在statement中使用字符串拼接,可讀性和維護性比較差。
二、其具備預編譯機制,性能比statement更快。
三、其可以有效防止SQL注入攻擊。
相同點:兩者都可以執行增長、刪除、修改等操做。
不一樣點:
一、execute能夠執行查詢語句,而後經過getResult把結果取出來。executeUpdate不能執行查詢語句。
二、execute返回Boolean類型,true表示執行的是查詢語句,false表示執行的insert、delete、update等。executeUpdate的返回值是int,表示有多少條數據受到了影響。
前面的入門案例中這裏返回的結果集是ResultSetImpl
ResultSetImpl類圖
SQL類型
Jdbc對應方法
返回類型
bit(1),bit(n)
getBoolean,getBytes()
Boolean,byte[]
tinyint
getByte()
Byte
smallint
getShort()
Short
int
getInt
Int
bigint
getLong()
Long
char,varchar,longvarchar
getString
String
text(clob) blob
getClob(),getblob()
Clob,blob
date
getDate()
java.sql.Date
time
getTime()
java.sql.Time
timestamp
getTimestamp
java.sql.Timestamp
以上即是結果集的處理,就這麼多了。
資源關閉不在業務代碼這一塊,主要是針對一些資源進行關閉,省得一直持有資源。另外咱們處理的資源關閉通常都是在finally中處理。
本文主要講了以下內容: