上一篇文章我們講了MySQL網路協議分析,包括如何與MySQL進行通訊,資料包的格式等內容,今天我主要會講如何設計一個MySQL解析包類庫(類似mysql-connector-xxx山寨版),本篇文章不具備實際使用意義,更多的是一種架構的設計的嘗試以及可以幫助大家理解一些相應第三方包的設計,為未來更從容的應對工作中遇到的問題。
文章概述
我會從最開始的資料庫連線到最終的資料獲取一系列步驟的講解,輔助示例程式碼用Java編寫,本文的主要幾個方面分別是:
- 資料包模型類設計
- 資料包解析類設計
- 相關網路傳輸類設計
- 相關編碼工具類設計
資料包模型類設計
資料包模型類設計主要是將資料庫傳輸給我們的資料包解析成我們程式中的模型類,好比你實際業務中建立的JavaBean,這些類的結構依賴於上一篇文章中解析的資料包內容,相關細節請參考上篇文章MySQL網路協議分析,根據具體的資料內容我們可以構建以下模型類:
- EOF(標誌類)
- Error(錯誤類)
- Field(資料列資訊類)
- Handshake (初始化類)
- Ok(Ok類)
- Parameter(錯誤類)
- PSOK(預處理執行OK類)
- ResultSetHeader(查詢結果集頭部類)
- RowData(具體列資料結構類)
- RowDataBinary(Binary資料結構類)
相信看過我前篇文章的同學,對上面很多類應該比較熟悉了,比如我們定義一個OK為以下結構:
public class OK implements Packet {
private long affectedRows; //影響行數
private long insertId; //自增id
private int serverStatus; //伺服器狀態
private String message; //額外資訊
...
}
複製程式碼
其他一些相關類的結構我這邊就不在貼出了,有興趣的同學可以參考mysql-connector-java包原始碼,或者看我的github專案也行(搬磚有點不好意思...),這部分內容是怎麼解析的基礎結構,充分掌握有助於後續的理解。
資料包解析類設計
假設我們現在已經寫好了解析後的資料包模型類,那麼我們怎麼將最原始的位元組資料轉換成這些類呢?首先我們分析解析的過程中需要哪些東西。
- 需要知道解析包的型別;
- 包的大小;
- 包結構欄位定義長度,當前解析資料塊位置;
- 臨時Buffer
- 解析結果儲存
這些元素是解析主要的需要的主要結構,可能還有一些其他的內容這裡就不闡述了,所以我們可以設計下面的解析類:
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協議中的編碼方式,主要有以下三個方法:
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類,它是程式中與資料庫伺服器進行互動的最重要的類,我們可以描述一下它有以下的幾點功能:
- 1.建立資料庫連線;
- 2.檢驗使用者,登入等;
- 3.執行相應的資料庫命令,增刪改查等;
- 4.接收資料庫傳來的資料包,並利用Parser類進行解析;
- 5.關閉資料庫連線;
基於這些需求我們可以構建出如下的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編碼了,方法的具體實現取決於實際中程式的資料型別和編碼協議可參考我的上一篇文章。
總結
這篇文章主要講解了如何去設計一個簡單的資料庫驅動,它最基本應該具備些什麼,各個模組間又是怎麼搭配的,這些內容不僅僅讓我們瞭解與資料庫通訊的步驟,也可以讓我們對目前使用的第三方資料庫驅動有更深入的瞭解,最後我會畫一張圖裡梳理了一下所有模組間的聯絡,幫助大家理解:
![mysql-connection](https://i.iter01.com/images/12d2edf32a915b3054513dfbcc12c4c2812e5987806404f571dc95747ba7c4cc.png)
![Asyncdb(五):MySQL驅動架構設計](https://i.iter01.com/images/2c1c50b5acaa36a9bfcb88514c4cd51eedec8d6366001c30383ab866e69da8ec.jpg)