天天看点

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

常见的数据源组件都实现了javax.sql.DataSource接口;

MyBatis不但要能集成第三方的数据源组件,自身也提供了数据源的实现;

一般情况下,数据源的初始化过程参数较多,比较复杂;

(采用工厂模式)

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

其实mybatis采用工厂模式,也是为了能适应更多的第三方数据源组件,如果有新的组件进来,只需要写类实现就行,工厂模式的优点很多,比如Spring的ioc,就是把对象的创建和对象的使用分离开来,解耦,new关键字耦合太多,对象的创建和管理由spring来做,而对象的使用是开发人员自己。

数据源模块的类图:

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

看一下DataSourceFactory接口:

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

这个接口的实现类有:

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

DataSourceFactory的实现类主要看 UnpooledDataSourceFactory这个类,也是一个工厂类。是一个不用连接池的数据链接工厂。

为什么说数据源很复杂,因为属性太多了,一般配置驱动。数据库url。用户名,密码等等实际上还可以配置很多

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

来看属性DataSource,这个也是一个接口

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

看DataSource接口的实现类PoolDataSource,使用连接池的数据源。

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

这个类还有很多初始化的构造函数,全是重载的。

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

所以才使用工厂来创建DataSource的对象/

再来回到这个图,根据代码也可以看到PooledDataSourceFactory继承了UnPooledDataSourceFactory

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

注意把逻辑分清楚:后缀带Factory的都是工厂类,不过有的是Unpool,有的是pool,而且pool的工厂类继承Unpool的工厂类,而Unpool的工厂类是用来生产Unpool的数据源,pool的工厂类是用来生产pool的数据源,看下图由虚线箭头的creat标志。

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

看UnpooledDataSource这个类,implements DataSource接口

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

这个问题来Debug一下:写一个Demo类,打断点

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

在UnpooledDataSource类的static的代码块中打上断点:

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

在DriverManager类的static的代码块中打上断点:

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

之后断点一直进入到上图LoadInitialDrivers();

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

然后进入这个load方法,打上断点

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

因为参数是Driver.class,所以ServiceLoader的常量service = Driver.class

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

那么来看构造方法

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

可以看到调用reload(),里面就是调用了new LazyIterator(service,loader),这是ServiceLoader类的内部类。

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

然后跳过,一直回到

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

在这个while循环里面跟进去, (上图是跟完以后的,下图是具体跟的步骤)

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

PREFIX 和 service 是什么 看下图:

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

然后 就去 这个目录下

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

parse解析

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

返回true ,然会while循环才进去

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

跟进next()

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

返回true,继续

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

就是在这块进行的。Class.forName,

然后CopyOnWritelist里面就保存了mysql驱动

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

这块ServiceLoader.load()属于Java SPI的知识,其本质就是服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 java.util.ServiceLoader工具类方法会使用ClassLoad类的getResources方法获取指定目录下的文件,读取文件内容并解析获取所有接口实现类的全限路径,根据全限路径使用class.forName装载class文件到JVM,然后通过c.newInstance()实例化类转化接口类型并放入providers缓存提供者集合中,以供后面使用。上面这段话是摘自

JavaSPI,ServiceLoader.load方法详解_hupoling的专栏-CSDN博客_serviceloader.load​blog.csdn.net

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

有兴趣的去看看,我的源码解读也很清楚

注意,这是mybatis的加载方式,jdbc的方式调用链还不太一样。可以写一个测试类用Class.forName()去debug看一下。

unpooledDatasource就是不用连接池的数据源,每次都是创建新的数据库连接。而pooledDatasource则不一样,会去从池子里找。

接下来看一下UnpooledDatasource是怎么产生数据库连接的

当 <dataSource>的type属性被配置成了”UNPOOLED”,MyBatis首先会实例化一个UnpooledDataSourceFactory工厂实例,然后通过.getDataSource()方法返回一个UnpooledDataSource实例对象引用,使用UnpooledDataSource的getConnection(),每调用一次就会产生一个新的Connection实例对象。

看一下:UnpooledDataSource的getConnection()

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

1.

初始化驱动:

判断driver驱动是否已经加载到内存中,如果还没有加载,则会动态地加载driver类,并实例化一个Driver对象,使用DriverManager.registerDriver()方法将其注册到内存中,以供后续使用。

2.

创建Connection对象:

使用DriverManager.getConnection()方法创建连接。

3.

配置Connection对象:

设置是否自动提交autoCommit和隔离级别isolationLevel。

4. 返回Connection对象。

看上图方法的第一行代码: initializeDriver(),画红框的不用多说了。

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

接着第二行代码:

Connection connection = DriverManager.getConnection(

url

, properties);

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
每次都要生成新的connection, 开销很大,这就是没有连接池的数据源

下来看有连接池的数据源 PooledDataSource 一个简单,同步的、线程安全的数据库连接池,基于PooledConnection和PoolState

有几个对象需要知道:

1、PooledConnection:

使用动态代理封装了真正的数据库连接对象Connection;

可以看到 PooledConnection 实现了 invocationhandler,动态代理。

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

真正的数据库连接对象是 Connection,作用是:连接数据库,获取查询数据,更新数据

那么 PooledConnection 是想对Connection进行增强。直接看invoke方法

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块
2、PoolState:

用于管理PooledConnection对象状态的组件,通过两个list分别 管理空闲状态的连接资源和活跃状态的连接资源

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

千万记住这两个ArrayList,以及ArrayList的remove方法的使用.

getConnection()获取连接的流程图

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

看一下获取有连接池的数据源 -> 获取 -> 数据库连接的代码:

PooledDataSource.getConnection()

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

获取连接就是在这个红框的 popConnection方法中做的。代码很长,逻辑不难,if - else判断比较多,和流程图一样,完全可以看懂,就不做分析了。

popConnection的代码:

一个数据表连接两个数据源_Mybatis源码分析(二): 数据源模块

注意这个数据结构,list是有序的集合,而remove(0)就是移除第一个并且返回。剩下的就不再分析了,代码很简单。关闭连接也有相应的代码,具体就不做分析了。