偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

沒(méi)想到你是這樣的JDBC

開(kāi)發(fā) 開(kāi)發(fā)工具
本文將介紹 MySQL Client 與 Server 的通信原理,以及 Java JDBC 的工作原理等。什么是JDBC 的 Type4,什么又是 Type 3?

本文將介紹 MySQL Client 與 Server 的通信原理,以及 Java JDBC 的工作原理等。什么是JDBC 的 Type4,什么又是 Type 3? 

一、 MySQL Client & Server

我們?cè)谶M(jìn)行數(shù)據(jù)庫(kù)的操作時(shí),總是通過(guò) GUI 數(shù)據(jù)管理工具,或者命令行連接到 MySQL 的 Server 上,然后進(jìn)行一系列數(shù)據(jù)庫(kù)的創(chuàng)建、表與表內(nèi)數(shù)據(jù)的操作等。

這個(gè)時(shí)候,這一系列 GUI管理工具,或者命令行,都是一個(gè) MySQL 的 Client, 然后將 Client 的一系列操作命令,發(fā)送給 Server。 這里在發(fā)送時(shí),Client 的命令都是根據(jù) MySQL 規(guī)范,生成的一個(gè)個(gè)packet進(jìn)行發(fā)送。

更直觀的理解, MySQL 的 Client 和 Server 相當(dāng)于是 Socket 通信中的一個(gè) Client 與 Server, 彼此按照約定的協(xié)議格式進(jìn)行通信。

二、 JDBC 是什么?

什么是 JDBC 呢? 你一定會(huì)脫口而出,不就是通過(guò)它連庫(kù)嘛。 這么理解只是其中的一小部分,「灑灑水的啦」。

JDBC 全稱(chēng):The Java Database Connectivity,要從兩個(gè)方面來(lái)理解。

  • API
  • Driver

API , 首先是一個(gè)標(biāo)準(zhǔn),并不針對(duì)特定的數(shù)據(jù)庫(kù),做為一個(gè)高層抽象,提供Java 語(yǔ)言與眾多數(shù)據(jù)庫(kù)之間的連通。 通過(guò)JDBC API,我們不再需要根據(jù)不同的數(shù)據(jù)庫(kù)使用不同的操作方式,而是以一種標(biāo)準(zhǔn)的操作,實(shí)現(xiàn)『Write Once, Run anywhere』。

既然 API 是個(gè)標(biāo)準(zhǔn),就需要有相對(duì)應(yīng)的實(shí)現(xiàn), 這里的 Driver 就是各個(gè)數(shù)據(jù)庫(kù)廠商根據(jù)標(biāo)準(zhǔn)進(jìn)行的針對(duì)實(shí)現(xiàn)。這也是為什么在應(yīng)用開(kāi)發(fā)時(shí),連MySQL 使用 MySQL 的 connector,連接 Oracle 使用 Oracle 的驅(qū)動(dòng)的原因。

畢竟如何和自己廠家的數(shù)據(jù)庫(kù)交互,只有各個(gè)廠商自己清楚,所以根據(jù)標(biāo)準(zhǔn),各個(gè)廠商開(kāi)發(fā)自己的 Connector。

下圖來(lái)自官方文檔,來(lái)描述 JDBC 的作用以及請(qǐng)求中所處的位置。

 JDBC 的作用以及請(qǐng)求中所處的位置

圖的左側(cè),也稱(chēng)為T(mén)ype4, 是通過(guò)Driver 直接連接數(shù)據(jù)庫(kù) Server。這種也是最常用的,通過(guò)Driver ,將JDBC 的請(qǐng)求轉(zhuǎn)成數(shù)據(jù)庫(kù)服務(wù)器可以識(shí)別的協(xié)議格式。

圖的右側(cè), 稱(chēng)為T(mén)ype 3 是通過(guò)Driver,將JDBC 的請(qǐng)求轉(zhuǎn)成 中間件的協(xié)議格式。

以MySQL為例,看到這里我們發(fā)現(xiàn),其實(shí) JDBC 的操作,本質(zhì)上相當(dāng)于是一個(gè) MySQL 的 Client,通過(guò) Driver,把應(yīng)用里的查詢(xún)、刪除等操作「翻譯」成了 MySQL Server 可識(shí)別的協(xié)議格式,再傳遞過(guò)去執(zhí)行。

所以,整個(gè)JDBC 做的事情可以歸結(jié)為以下三件:

  1. 創(chuàng)建數(shù)據(jù)庫(kù)連接
  2. 發(fā)送 SQL statement
  3. 處理請(qǐng)求結(jié)果

JDBC 總結(jié)起來(lái)的兩個(gè)部分,數(shù)據(jù)庫(kù)服務(wù)提供方,開(kāi)發(fā)XXXDriver, 應(yīng)用開(kāi)發(fā)者使用Driver 連接數(shù)據(jù)庫(kù),進(jìn)行數(shù)據(jù)庫(kù)操作。

這樣應(yīng)用開(kāi)發(fā)者就不需要關(guān)心底層與數(shù)據(jù)庫(kù)交互時(shí)的協(xié)議實(shí)現(xiàn),如何進(jìn)行請(qǐng)求連接,交互等,可以更專(zhuān)心到自己的業(yè)務(wù)。 否則,每個(gè)開(kāi)發(fā)者都需要處理一次和數(shù)據(jù)交互的協(xié)議,繁瑣而且不易,重復(fù)勞動(dòng)。

三、MySQL connector-J 部分源碼

有了上述的「理論」知識(shí)后,我們來(lái)看點(diǎn)干的。 MySQL 的驅(qū)動(dòng)包是開(kāi)源的,我們可以很方便的進(jìn)行下載了解實(shí)現(xiàn)。

最傳統(tǒng)的 JDBC 使用,一般都是通過(guò)以下這種方式:

  • Connection c = DriverManager.getConnection(url, user,pwd);
  • Statement stmt = c.createStatment
  • stmt.executeQuery 拿結(jié)果

getConnection的時(shí)候一般都需要提供一個(gè)URL,這個(gè)URL也都是固定寫(xiě)法,比如mysql的是 jdbc:mysql://,這一部分是按照規(guī)范,同時(shí)在Driver的代碼里,通過(guò)解析URL獲取要連接到的主機(jī),端口,以及其他的連接參數(shù)。

  1. public Properties parseURL(String url, Properties defaults) throws java.sql.SQLException { 
  2.         Properties urlProps = (defaults != null) ? new Properties(defaults) : new Properties(); 
  3.         if (url == null) { 
  4.             return null; 
  5.         } 
  6.         if (!StringUtils.startsWithIgnoreCase(url, URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX) 
  7.                 && !StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) { 
  8.             return null; 
  9.         } 
  10.         int beginningOfSlashes = url.indexOf("//"); 
  11.         if (StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)) { 
  12.             urlProps.setProperty("socketFactory", "com.mysql.management.driverlaunched.ServerLauncherSocketFactory"); 
  13.         } 

看這一部分源碼可以發(fā)現(xiàn),除了我們常用的url配置,還可以在其中進(jìn)行l(wèi)oadbalance的配置等等。長(zhǎng)了見(jiàn)識(shí)。

  1. DriverManager.getConnection(xx,xx,xx) 這個(gè)方法最終會(huì)調(diào)用 Service Provider 已經(jīng)加載的 Driver中可用的driver,調(diào)用driver的getConnection方法,對(duì)應(yīng)到Mysql的源碼,就是下方這個(gè),重點(diǎn)是`com.mysql.jdbc.ConnectionImpl.getInstance` 
  2.  
  3. public java.sql.Connection connect(String url, Properties info) { 
  4.         if (url == null) { 
  5.             throw SQLError.createSQLException(Messages.getString("NonRegisteringDriver.1"), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null); 
  6.         } 
  7.         if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) { 
  8.             return connectLoadBalanced(url, info); 
  9.         } else if (StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) { 
  10.             return connectReplicationConnection(url, info); 
  11.         } 
  12.         Properties props = null
  13.         if ((props = parseURL(url, info)) == null) { 
  14.             return null; 
  15.         } 
  16.         if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) { 
  17.             return connectFailover(url, info); 
  18.         } 
  19.         try { 
  20.             Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(host(props), port(props), props, database(props), url); 
  21.             return newConn; 

再來(lái)看 getInstance具體做了啥?

  1. protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) 
  2.             throws SQLException { 
  3.         if (!Util.isJdbc4()) { 
  4.             return new ConnectionImpl(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url); 
  5.         } 
  6.         return (Connection) Util.handleNewInstance(JDBC_4_CONNECTION_CTOR, 
  7.                 new Object[] { hostToConnectTo, Integer.valueOf(portToConnectTo), info, databaseToConnectTo, url }, null); 
  8.     } 
  1. this.io = new MysqlIO(newHost, newPort, mergedProps, getSocketFactoryClassName(), getProxy(), getSocketTimeout(), 
  2.                 this.largeRowSizeThreshold.getValueAsInt()); 
  3. this.io.doHandshake(this.user, this.password, this.database); 

我們看,先通過(guò)MysqlIO創(chuàng)建了一個(gè)IO連接,然后進(jìn)行握手

  1. // save last exception to propagate to caller if connection fails 
  2.                 SocketException lastException = null
  3.                 // Need to loop through all possible addresses. Name lookup may return multiple addresses including IPv4 and IPv6 addresses. Some versions of 
  4.                 // MySQL don't listen on the IPv6 address so we try all addresses. 
  5.                 for (int i = 0; i < possibleAddresses.length; i++) { 
  6.                     try { 
  7.                         this.rawSocket = createSocket(props); // 這里創(chuàng)建了一個(gè)空的Socket對(duì)象 
  8.                         configureSocket(this.rawSocket, props); //將一些超時(shí)之類(lèi)的屬性設(shè)置到socket中 
  9.                         InetSocketAddress sockAddr = new InetSocketAddress(possibleAddresses[i], this.port); //獲取host對(duì)應(yīng)的ip地址等,再加上端口,組成一個(gè)Address 
  10.                         // bind to the local port if not using the ephemeral port 
  11.                         if (localSockAddr != null) { 
  12.                             this.rawSocket.bind(localSockAddr); 
  13.                         } 
  14.                         this.rawSocket.connect(sockAddr, getRealTimeout(connectTimeout)); //實(shí)際連接到服務(wù)器 

連接Mysql的url中,可以分成好幾類(lèi),例如可以連接到mysql進(jìn)行l(wèi)oadbalanner, jdbc:mysql:loadbalancer//xxx 還有進(jìn)行replicated

我們?cè)谑褂肑DBC連接時(shí),一定會(huì)常使用PreparedStatement, 這個(gè)稱(chēng)為預(yù)編譯sql,其中可以設(shè)置一些占位符

那這些占位符是啥時(shí)候填充進(jìn)去的呢?

查看Mysql Connector 的源碼,我們發(fā)現(xiàn),實(shí)際前面的createPreparedStatment,setXX之類(lèi)的時(shí)候,

只是設(shè)置到對(duì)應(yīng)的變量里記錄了下來(lái),

在執(zhí)行executeQuery的時(shí)候,會(huì)再?gòu)那懊嬗涗浵聛?lái)的變理中提取出來(lái),做為值填充到原來(lái)的sql占位中去

整個(gè)sql做為一個(gè)packet發(fā)送過(guò)去。

這個(gè)時(shí)候也就更容易理解為啥預(yù)編譯不容易被SQL 注入,而拼接SQL容易。 因?yàn)轭A(yù)編譯在替換占位符時(shí),即使你的值里有類(lèi)似于 「--」 這一類(lèi)的危險(xiǎn)內(nèi)容,或者 1==1, 都是做為一個(gè)column的value 來(lái)使用,而拼接SQL,則會(huì)放到完整的語(yǔ)句中,在執(zhí)行時(shí)被全部解析,導(dǎo)致問(wèn)題。

以下就是 MySQL Connector 在執(zhí)行 sql 時(shí)的調(diào)用棧。

 

  1. java.lang.Thread.State: RUNNABLE 
  2.   at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3633) 
  3.   at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2460) 
  4.   at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2625) 
  5.   at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2551) 
  6.   - locked <0x5a3> (a com.mysql.jdbc.JDBC4Connection) 
  7.   at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1861) 
  8.   at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1962) 

 

整個(gè)背后其實(shí)原理也和我們前面說(shuō)的一樣,比較簡(jiǎn)單,是通過(guò)一個(gè)TCP Socket 方式,在獲取到OutputStream,接裝好的SQL,

在執(zhí)行的時(shí)候,是寫(xiě)到這個(gè)Output里,發(fā)送到 Mysql的服務(wù)器。

返回值是怎么獲取的呢? 是將返回的Buffer轉(zhuǎn)換成ResultSet

 

  1. ResultSetInternalMethods rs = readAllResults(callingStatement, maxRows, resultSetType, resultSetConcurrency, streamResults, catalog, resultPacket, 
  2.                   false, -1L, cachedMetadata); 

 

此外,在實(shí)際的業(yè)務(wù)開(kāi)發(fā)中,對(duì)于在代碼中拿到的一個(gè)Connection,可能會(huì)遇到網(wǎng)絡(luò)抖動(dòng),數(shù)據(jù)庫(kù)服務(wù)異常等情況。有連接問(wèn)題之前,我們可以先檢測(cè)連接是否可用,來(lái)避免繼續(xù)使用有問(wèn)題的Connection,導(dǎo)致問(wèn)題一直存在。

檢測(cè)一個(gè)連接是否可用,可以通過(guò)執(zhí)行一條最簡(jiǎn)單的 `select 1` 來(lái)判斷是否有異常,當(dāng)然,在JDBC的標(biāo)準(zhǔn)里,也包含一個(gè)檢查連接是否可用的方法 isValid

實(shí)現(xiàn)原理,對(duì)于MySQL 的Connctor-J客戶(hù)端,是通過(guò)向Server發(fā)送一條ping的命令,來(lái)檢測(cè)連接的狀態(tài)。

總結(jié)一下,我們通過(guò)幾個(gè)部分來(lái)介紹了 MySQL Client 與 Server 的交互原理,以及JDBC 是什么,是通過(guò)什么方式來(lái)和 Server 進(jìn)行交互的。

順道再分享下最近遇到的一個(gè)和數(shù)據(jù)庫(kù)連接有關(guān)的小插曲。在處理一個(gè)問(wèn)題,增加數(shù)據(jù)庫(kù)連接檢查之后,功能正確就上線了。上線不久,接到另一個(gè)服務(wù)提供方報(bào)警,說(shuō)我們發(fā)送了其不能處理的數(shù)據(jù)庫(kù)指令。 黑人問(wèn)號(hào)臉。我只是通過(guò)獲取數(shù)據(jù)庫(kù)狀態(tài)的一個(gè)getAttribute的方式來(lái)檢查下連接啊。 據(jù)說(shuō)他們收到的是show xxx status之類(lèi)的指令。 那為啥不能識(shí)別呢?

仔細(xì)問(wèn)了一下,是由于他們提供的特殊 Proxy 服務(wù),只實(shí)現(xiàn)了MySQL 的部分指令解析,所以對(duì)應(yīng)show xxx 不支持,而我們項(xiàng)目里默認(rèn)以為全部的client 都支持全集指令,導(dǎo)致問(wèn)題。之后改了一個(gè)檢查方式解決了報(bào)警問(wèn)題。

所以,在開(kāi)發(fā)時(shí),也需要再考慮下接入的服務(wù),是否會(huì)按照規(guī)范,把全部?jī)?nèi)容實(shí)現(xiàn)了。

【本文為51CTO專(zhuān)欄作者“侯樹(shù)成”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)『Tomcat那些事兒』獲取授權(quán)】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來(lái)源: 51CTO專(zhuān)欄
相關(guān)推薦

2019-03-08 10:08:41

網(wǎng)絡(luò)程序猿代碼

2018-05-02 09:38:02

程序員代碼互聯(lián)網(wǎng)

2023-02-26 00:00:02

字符串分割String

2019-08-19 09:21:36

程序員Bug代碼

2018-06-27 14:23:38

機(jī)器學(xué)習(xí)人工智能入門(mén)方法

2021-01-27 18:13:35

日志nginx信息

2016-03-04 14:14:02

電話免費(fèi)越洋

2017-12-26 15:41:26

2018-12-26 09:44:02

分布式緩存本地緩存

2022-03-21 08:55:53

RocketMQ客戶(hù)端過(guò)濾機(jī)制

2024-01-04 12:33:17

ChatGPTAI視頻

2012-12-28 13:47:36

Raspberry PGeek

2022-01-05 17:13:28

監(jiān)控HTTPS網(wǎng)站

2017-02-09 17:00:00

iOSSwiftKVC

2021-11-29 05:37:24

Windows Def操作系統(tǒng)微軟

2020-08-14 08:19:25

Shell命令行數(shù)據(jù)

2009-04-28 07:48:29

蓋茨打工基金會(huì)

2022-11-02 07:46:31

GoFrameGcache緩存

2023-09-07 06:48:38

Intel顯卡AMD

2020-12-31 06:12:38

Siri Windows電腦
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)