标签:void epo 情况下 tty ade for maximum 现在 分库分表
本文,我们来分享 MyBatis 的数据源模块,对应 datasource
包。如下图所示:
在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中,简单介绍了这个模块如下:
数据源是实际开发中常用的组件之一。现在开源的数据源都提供了比较丰富的功能,例如,连接池功能、检测连接状态等,选择性能优秀的数据源组件对于提升 ORM 框架乃至整个应用的性能都是非常重要的。
MyBatis 自身提供了相应的数据源实现,当然 MyBatis 也提供了与第三方数据源集成的接口,这些功能都位于数据源模块之中。
本文涉及的类如下图所示:
下面,我们就一起来看看具体的源码实现。
org.apache.ibatis.datasource.DataSourceFactory
,javax.sql.DataSource
工厂接口。代码如下:
public interface DataSourceFactory {
|
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
,实现 DataSourceFactory 接口,非池化的 DataSourceFactory 实现类。
FROM 《MyBatis 文档 —— XML 映射配置文件》
UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。 不同的数据库在性能方面的表现也是不一样的,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:
driver
– 这是 JDBC 驱动的 Java 类的完全限定名(并不是 JDBC 驱动中可能包含的数据源类)。url
– 这是数据库的 JDBC URL 地址。username
– 登录数据库的用户名。password
– 登录数据库的密码。defaultTransactionIsolationLevel
– 默认的连接事务隔离级别。作为可选项,你也可以传递属性给数据库驱动。要这样做,属性的前缀为“driver.”,例如:
driver.encoding=UTF8
这将通过
DriverManager.getConnection(url,driverProperties)
方法传递值为UTF8
的encoding
属性给数据库驱动。
// UnpooledDataSourceFactory.java
|
#getDataSource()
方法,返回 DataSource 对象。代码如下:
// UnpooledDataSourceFactory.java
|
#setProperties(Properties properties)
方法,将 properties
的属性,初始化到 dataSource
中。代码如下:
// UnpooledDataSourceFactory.java
|
<1>
处,调用 #convertValue(MetaObject metaDataSource, String propertyName, String value)
方法,将字符串转化成对应属性的类型。代码如下:
// UnpooledDataSourceFactory.java
|
org.apache.ibatis.datasource.pooled.PooledDataSourceFactory
,继承 UnpooledDataSourceFactory 类,池化的 DataSourceFactory 实现类。
FROM 《MyBatis 文档 —— XML 映射配置文件》
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
poolMaximumActiveConnections
– 在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10poolMaximumIdleConnections
– 任意时间可能存在的空闲连接数。poolMaximumCheckoutTime
– 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)poolTimeToWait
– 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)。poolMaximumLocalBadConnectionTolerance
– 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程. 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections
与poolMaximumLocalBadConnectionTolerance
之和。 默认值:3 (新增于 3.4.5)poolPingQuery
– 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息。poolPingEnabled
– 是否启用侦测查询。若开启,需要设置poolPingQuery
属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。poolPingConnectionsNotUsedFor
– 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
代码如下:
// PooledDataSourceFactory.java
|
其它方法,在父类中 UnpooledDataSourceFactory 中已经实现。所以,真正的池化逻辑,在 PooledDataSource 对象中。
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
,实现 DataSourceFactory 接口,基于 JNDI 的 DataSourceFactory 实现类。
FROM 《MyBatis 文档 —— XML 映射配置文件》
JNDI – 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。这种数据源配置只需要两个属性:
initial_context
– 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找。data_source
– 这是引用数据源实例位置的上下文的路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给初始上下文。比如:
env.encoding=UTF8
这就会在初始上下文(InitialContext)实例化时往它的构造方法传递值为
UTF8
的encoding
属性。
// JndiDataSourceFactory.java
|
dataSource
不在构造方法中创建,而是在 #setProperties(Properties properties)
中。#getDataSource()
方法,返回 DataSource 对象。代码如下:
// JndiDataSourceFactory.java
|
#setProperties(Properties properties)
方法,从上下文中,获得 DataSource 对象。代码如下:
// JndiDataSourceFactory.java
|
<1>
处,调用 #getEnvProperties(Properties allProps)
方法,获得系统 Properties 对象。代码如下:
// JndiDataSourceFactory.java
|
javax.sql.DataSource
是个神奇的接口,在其上可以衍生出数据连接池、分库分表、读写分离等等功能。
如果你对 DataSource 如何实现分库分表的功能,可以看看 《Sharding-JDBC 源码分析 —— JDBC实现与读写分离》 。
org.apache.ibatis.datasource.unpooled.UnpooledDataSource
,实现 DataSource 接口,非池化的 DataSource 对象。
// UnpooledDataSource.java
|
#getConnection(...)
方法,获得 Connection 连接。代码如下:
// UnpooledDataSource.java
|
都是调用 #doGetConnection(String username, String password)
方法,获取 Connection 连接。代码如下:
// UnpooledDataSource.java
|
<1>
处,调用 #initializeDriver()
方法,初始化 Driver 。详细解析,见 「3.1.2.1 initializeDriver」 。<2>
处,调用 java.sql.DriverManager#getConnection(String url, Properties info)
方法,获得 Connection 对象。<3>
处,调用 #configureConnection(Connection conn)
方法,配置 Connection 对象。详细解析,见 「3.1.2.2 configureConnection」 。#initializeDriver()
方法,初始化 Driver 。代码如下:
// UnpooledDataSource.java
|
registeredDrivers
是否已经存在该 driver
?若不存在,进行初始化。<1>
处,synchronized
锁的粒度太大,可以减小到基于 registeredDrivers
来同步,并且很多时候,不需要加锁。<2>
处,获得 driver
类,实际上,就是我们常见的 "Class.forName("com.mysql.jdbc.Driver")"
。<3>
处,创建 Driver 对象,并注册到 DriverManager 中,以及添加到 registeredDrivers
中。为什么此处会有使用 DriverProxy 呢?DriverProxy 的代码如下:
// UnpooledDataSource.java 的内部私有静态类
|
<4>
处,使用 MyBatis 自定义的 Logger 对象。driver
对应的方法。#configureConnection(Connection conn)
方法,配置 Connection 对象。代码如下:
// UnpooledDataSource.java
|
UnpooledDataSource 还实现了 DataSource 的其它方法,感兴趣的胖友,可以自己看。实际上,不看也行。哈哈哈哈。
org.apache.ibatis.datasource.pooled.PooledDataSource
,实现 DataSource 接口,池化的 DataSource 实现类。
FROM PooledDataSource 类上的注释
This is a simple, synchronous, thread-safe database connection pool.
// PooledDataSource.java
|
dataSource
属性,UnpooledDataSource 对象。这样,就能重用 UnpooledDataSource 的代码了。说白了,获取真正连接的逻辑,还是在 UnpooledDataSource 中实现。expectedConnectionTypeCode
属性,调用 #assembleConnectionTypeCode(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties)
方法,计算 expectedConnectionTypeCode
的值。代码如下:
// PooledDataSource.java
|
state
属性,PoolState 对象,记录池化的状态。这是一个非常重要的类,下文也会花一定的篇幅,详细解析。#getConnection(...)
方法,获得 Connection 连接。代码如下:
// PooledDataSource.java
|
#popConnection(String username, String password)
方法,获取 org.apache.ibatis.datasource.pooled.PooledConnection
对象,这是一个池化的连接。非常关键的一个方法,详细解析,见 「3.2.2.1 popConnection」 。PooledConnection#getProxyConnection()
方法,返回代理的 Connection 对象。这样,每次对数据库的操作,才能被 PooledConnection 的 「5.2 invoke」 代理拦截。#popConnection(String username, String password)
方法,获取 PooledConnection 对象。
整体流程如下图:
FROM 《MyBatis 技术内幕》
代码如下:
// PooledDataSource.java |