我们以JDBC的常见的使用示例作为切入口,如下所示:
jdbc是属于一种插件式的设计,意思是jdbc定义了一组接口,程序开发者将面向这组接口去使用它jdbc,而接口的具体实现则由数据库服务商来提供。这样,jdbc良好地把实现进行隐藏,如果你想操作MySQL数据库,那么就引入MySQL实现地依赖包实现可拔插的效果,其它数据库类似(本文以MySQL为例)。
引入了依赖包,程序开发者怎么去使用它呢?我们自上而下打开源码看一看
我们知道Class.forName()方法是jdk提供的,用来加载类并执行类的初始化。而入参"com.mysql.cj.jdbc.Driver"则指定了待加载的全限定名。所以Class.forName("com.mysql.cj.jdbc.Driver")这一行代码主要是为了加载MySQL实现的jdbc driver。
为了了解加载driver的过程做了什么,我们得看看com.mysql.cj.jdbc.Driver(这里看的是mysql-connector-j连接器代码)
Driver实现类继承子NonRegisteringDriver,在Driver的静态块中调用了DriverManager的静态方法,注册了一个Driver实例对象。
registerDriver方法将Driver包装成DriverInfo并添加到了一个CopyOnWriteArrayList静态集合当中
注册方法调用如下
到这里,我们可以了解到Class.forName将MySQL的Driver实例添加到了DriverManager维护的集合当中,也就是常说的“注册驱动”。
Driver注册到了DriverManager,那么开发人员将可以通过DriverManager提供的getConnection方法获取连接,核心实现代码如下
我们看到,该方法将遍历所有注册的驱动对象。拿到每个driver对象将会调用connect()方法去获取,connect()方法传入URL地址和用户密码信息,如果url地址不匹配,比如MySQL的地址连接Oracle的数据库,那么就会返回null。如果匹配上了,那么就会返回一个Connection的实例。下面我们看看MySQL实现的connection实例
MySQL将会根据URL地址来决定创建什么样的实例,单点的、故障转移、负载均衡还是主从复制,如
该方法属于Connection,以sql语句作为入参,调用该方法将返回一个PreparedStatement实例对象。
PreparedStatement的对象是参数化的sql语句对象,也就是说sql语句将预编译以后存储起来,并且可以多次高效调用。如果driver实例对象允许预编译,那么创建PreparedStatement对象的时候将发送到数据库去,而如果有的driver实现不允许预编译,那么只有等到下一步execute执行的时候SQL才会发送到数据库。
我们以ConnectionImpl为例,看看MySQL的代码实现,如下
首先它判断了server能否执行了预编译,然后获取一个实例对象,我们打开getInstance方法
该方法创建里一个实例对象,并在构造方法中调用了serverPrepare(String sql)方法,我们再打开serverPrepare(sql)跟踪以下它干了什么
serverPrepate(sql)方法调用了session的sendCommand(),最终调用的是Protocol接口的sendCommand方法发送到MySQLserver。
Protocol将调用MessageSender经过socket的IO流传送到写入到MySQLserver,我们这里简单打开一个MessageSender的实现类了解一下
而executeQuery的逻辑相对简单一点,经过一系列的校验和包装处理之后和PrepatedStatement一样调用MessageSender发送一个包到MysqlServer,然后读取返回结果,包装成一个ResultSet对象
ResultSet是一个接口,它主要是为了包装返回结果,由MySQL来实现接口的实例。熟悉迭代器模式的话,应该很容易理解它的这种指针设计。
每一次next()将把指针进行偏移,将下一个row设置为当前的row,再通过getString()等方法将结果获取。
jdbc的api将开发人员与数据库提供商进行了接口隔离设计,开发人员只需要面对jdbc接口无需关心怎么跟数据库交互,这本身大大简化了开发过程提升效率并且你可以清晰感受到人家是怎么“面向接口设计”的。
而jdbc在使用之前,只需要将驱动注册,而后开发人员将通过url来匹配驱动并连接server端。
同时,我们知道了jdbc通过socket的IO流方式连接数据库,所以这方面存在一定的消耗,即使提供商做了很多优化也不及内存调用来得快,因此在程序设计中应当考虑缓存等方式来减少数据库连接。