上一篇文章咱們講了MySQL網絡協議分析,包括如何與MySQL進行通訊,數據包的格式等內容,今天我主要會講如何設計一個MySQL解析包類庫(相似mysql-connector-xxx山寨版),本篇文章不具有實際使用意義,更多的是一種架構的設計的嘗試以及能夠幫助你們理解一些相應第三方包的設計,爲將來更從容的應對工做中遇到的問題。html
我會從最開始的數據庫鏈接到最終的數據獲取一系列步驟的講解,輔助示例代碼用Java編寫,本文的主要幾個方面分別是:java
數據包模型類設計主要是將數據庫傳輸給咱們的數據包解析成咱們程序中的模型類,比如你實際業務中創建的JavaBean,這些類的結構依賴於上一篇文章中解析的數據包內容,相關細節請參考上篇文章MySQL網絡協議分析,根據具體的數據內容咱們能夠構建如下模型類:mysql
相信看過我前篇文章的同窗,對上面不少類應該比較熟悉了,好比咱們定義一個OK爲如下結構:git
public class OK implements Packet {
private long affectedRows; //影響行數
private long insertId; //自增id
private int serverStatus; //服務器狀態
private String message; //額外信息
...
}
複製代碼
其餘一些相關類的結構我這邊就不在貼出了,有興趣的同窗能夠參考mysql-connector-java包源碼,或者看個人github項目也行(搬磚有點很差意思...),這部份內容是怎麼解析的基礎結構,充分掌握有助於後續的理解。github
假設咱們如今已經寫好了解析後的數據包模型類,那麼咱們怎麼將最原始的字節數據轉換成這些類呢?首先咱們分析解析的過程當中須要哪些東西。sql
這些元素是解析主要的須要的主要結構,可能還有一些其餘的內容這裏就不闡述了,因此咱們能夠設計下面的解析類:數據庫
public class Parser {
private List<Integer> waitFor = new LinkedList<Integer>(); //接下去要解析的包類型
private int dataIdx = 0; //當前解析數據包數據塊的索引
private ByteBuffer buffer = ByteBuffer.allocate(65536); //臨時Buffer
private int packetSize = -1; //包大小
private int itemSize = 0; //當前解析數據包數據塊的大小
private Packet packet = null; //解析結果
...
}
複製代碼
上面的屬性都是解析過程須要初始值,中間變量,結果等,除了這些屬性外,咱們還須要有將Buffer中數據轉換爲咱們須要數據的方法,根據MySQL協議中的編碼方式,主要有如下三個方法:bash
private void readNullTerminated(ByteBuffer in) { //對應NullTerminatedString(Null結尾方式): 字符串以遇到Null做爲結束標誌,相應的字節爲00。
...
}
private void readLengthCodedBinary(ByteBuffer in) { //對應LengthEncodedInteger編碼方式,根據第一個字節區分數據所佔的字節長度
...
}
private void readLengthCodedString(ByteBuffer in) { //對應LengthEncodedString編碼方式,字符串的值根據nteger + Value組成,經過計算Integer的值來獲取Value的具體的長度。
...
}
複製代碼
有了以上的屬性和相應方法後,咱們即可以將服務器傳來數據包解析成咱們想要的數據了。服務器
整理的網絡傳輸類其實就是咱們常見Connection類,它是程序中與數據庫服務器進行交互的最重要的類,咱們能夠描述一下它有如下的幾點功能:網絡
基於這些需求咱們能夠構建出以下的Connection類:
public class MysqlConnection {
private Handshake handshake; //握手初始包類
private final ByteBuffer out = ByteBuffer.allocate(65536); //發送給服務端的數據Buffer
private final ByteBuffer in = ByteBuffer.allocate(65536); // 接收的數據Buffer
private SocketChannel channel; //異步IO傳輸通道
private Parser parser = new Parser(); //解析類
private String host; //數據庫服務器host
private int port; //數據庫服務器端口
private String user; //數據庫用戶名
private String password; //數據庫密碼
private String database; //鏈接具體的數據庫
private Selector selector; //註冊channel的selector
private long connectionId = 0L; //鏈接id
}
複製代碼
這些屬性相應對數據庫驅動稍微有所瞭解的人都很是熟悉,由於日常寫程序常常跟他們打交道,有了上面這些屬性,咱們還須要一些方法,好比鏈接數據庫,執行命令,讀取數據,關閉數據庫等方法,因此能夠定義如下的一些方法:
private void connect() { //鏈接數據庫
...
}
public void auth () { //校驗帳戶
...
}
public void query () { //執行普通查詢
...
}
public void update () { //執行普通更新
...
}
public void executeQuery () { //執行預處理查詢
...
}
public void executeUpdate () { //執行預處理更新
...
}
public void read () { //讀取通道中的數據
...
}
private void send () { //向服務器發送數據
...
}
public void close() { //關閉數據庫
...
}
複製代碼
這都是一些必要且經常使用的方法,相信不少人在實際開發中都有所使用,有了以上的一些屬性和方法後咱們就能夠搭建出一個Connection類的基本模型,至於其餘一些對象,好比數據庫鏈接池,預處理對象都是基於最基礎的Connection。
其實上面的一些類與方法,已經能組裝成一個簡單的與數據庫交互的驅動,可是咱們知道,咱們向數據庫服務器發送指令的時候,並非向咱們直接在數據庫終端寫SQL執行那麼簡單,而是須要根據數據庫的相應協議將咱們須要執行的SQL翻譯成相應的字節流再發送給數據庫服務端,因此咱們必須有相關的編碼工具類,好比Long類型編碼,NULL值編碼等等,因此咱們須要寫相應的編碼類提升咱們對SQL編碼的效率,它應該具備如下的功能:
public static void longEncoded(){ //long類型編碼
...
}
public static void nullTerminatedStringEncoded() { //nullTerminatedString編碼
...
}
public static void lengthEncodedStringEncoded() { //lengthEncodedString編碼
...
}
private static void dateEncoded () { //Date類型編碼
...
}
複製代碼
一般來講,上面這四種編碼方式能夠實現大部分場景的SQL編碼了,方法的具體實現取決於實際中程序的數據類型和編碼協議可參考個人上一篇文章。
這篇文章主要講解了如何去設計一個簡單的數據庫驅動,它最基本應該具有些什麼,各個模塊間又是怎麼搭配的,這些內容不只僅讓咱們瞭解與數據庫通訊的步驟,也可讓咱們對目前使用的第三方數據庫驅動有更深刻的瞭解,最後我會畫一張圖裏梳理了一下全部模塊間的聯繫,幫助你們理解: