上一篇文章咱们讲了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编码了,方法的具体实现取决于实际中程序的数据类型和编码协议可参考个人上一篇文章。
这篇文章主要讲解了如何去设计一个简单的数据库驱动,它最基本应该具有些什么,各个模块间又是怎么搭配的,这些内容不只仅让咱们了解与数据库通讯的步骤,也可让咱们对目前使用的第三方数据库驱动有更深刻的了解,最后我会画一张图里梳理了一下全部模块间的联系,帮助你们理解: