MyBatis 源码分析 - 内置数据源

MyBatis 源码分析 - 内置数据源

1.简介

本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑。搞懂这些数据源的实现,可使大家对数据源有更深入的认识。同时在配置这些数据源时,也会更清楚每种属性的意义和用途。因此,如果大家想知其然,也知其所以然。那么接下来就让我们一起去探索 MyBatis 内置数据源的源码吧。

MyBatis 支持三种数据源配置,分别为 UNPOOLED、POOLED 和 JNDI。并提供了两种数据源实现,分别是 UnpooledDataSource 和 PooledDataSource。在三种数据源配置中,UNPOOLED 和 POOLED 是我们最常用的两种配置。至于 JNDI,MyBatis 提供这种数据源的目的是为了让其能够运行在 EJB 或应用服务器等容器中,这一点官方文档中有所说明。由于 JNDI 数据源在日常开发中使用甚少,因此,本篇文章不打算分析 JNDI 数据源相关实现。大家若有兴趣,可自行分析。接下来,本文将重点分析 UNPOOLED 和 POOLED 两种数据源。其他的就不多说了,进入正题吧。

2.内置数据源初始化过程

在详细分析 UnpooledDataSource 和 PooledDataSource 两种数据源实现之前,我们先来了解一下数据源的配置与初始化过程。现在看数据源是如何配置的,如下:

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dataSource</span> <span class="attr">type</span>=<span class="string">"UNPOOLED|POOLED"</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"driver"</span> <span class="attr">value</span>=<span class="string">"com.mysql.cj.jdbc.Driver"</span>/&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"url"</span> <span class="attr">value</span>=<span class="string">"jdbc:mysql..."</span>/&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"username"</span> <span class="attr">value</span>=<span class="string">"root"</span>/&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"password"</span> <span class="attr">value</span>=<span class="string">"1234"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dataSource</span>&gt;</span></span><br></pre></td></tr></tbody></table>

数据源的配置是内嵌在 <environment> 节点中的,MyBatis 在解析 <environment> 节点时,会一并解析数据源的配置。MyBatis 会根据具体的配置信息,为不同的数据源创建相应工厂类,通过工厂类即可创建数据源实例。关于数据源配置的解析以及数据源工厂类的创建过程,我在 MyBatis 配置文件解析过程一文中分析过,这里就不赘述了。下面我们来看一下数据源工厂类的实现逻辑。

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UnpooledDataSourceFactory</span> <span class="keyword">implements</span> <span class="title">DataSourceFactory</span> </span>{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DRIVER_PROPERTY_PREFIX = <span class="string">"driver."</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> DataSource dataSource;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">UnpooledDataSourceFactory</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 创建 UnpooledDataSource 对象</span></span><br><span class="line"> <span class="keyword">this</span>.dataSource = <span class="keyword">new</span> UnpooledDataSource();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setProperties</span><span class="params">(Properties properties)</span> </span>{</span><br><span class="line"> Properties driverProperties = <span class="keyword">new</span> Properties();</span><br><span class="line"> <span class="comment">// 为 dataSource 创建元信息对象</span></span><br><span class="line"> MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);</span><br><span class="line"> <span class="comment">// 遍历 properties 键列表,properties 由配置文件解析器传入</span></span><br><span class="line"> <span class="keyword">for</span> (Object key : properties.keySet()) {</span><br><span class="line"> String propertyName = (String) key;</span><br><span class="line"> <span class="comment">// 检测 propertyName 是否以 "driver." 开头</span></span><br><span class="line"> <span class="keyword">if</span> (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {</span><br><span class="line"> String value = properties.getProperty(propertyName);</span><br><span class="line"> <span class="comment">// 存储配置信息到 driverProperties 中</span></span><br><span class="line"> driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (metaDataSource.hasSetter(propertyName)) {</span><br><span class="line"> String value = (String) properties.get(propertyName);</span><br><span class="line"> <span class="comment">// 按需转换 value 类型</span></span><br><span class="line"> Object convertedValue = convertValue(metaDataSource, propertyName, value);</span><br><span class="line"> <span class="comment">// 设置转换后的值到 UnpooledDataSourceFactory 指定属性中</span></span><br><span class="line"> metaDataSource.setValue(propertyName, convertedValue);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> DataSourceException(<span class="string">"Unknown DataSource property: "</span> + propertyName);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (driverProperties.size() &gt; <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 设置 driverProperties 到 UnpooledDataSourceFactory 的 driverProperties 属性中</span></span><br><span class="line"> metaDataSource.setValue(<span class="string">"driverProperties"</span>, driverProperties);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">private</span> Object <span class="title">convertValue</span><span class="params">(MetaObject metaDataSource, String propertyName, String value)</span> </span>{</span><br><span class="line"> Object convertedValue = value;</span><br><span class="line"> <span class="comment">// 获取属性对应的 setter 方法的参数类型</span></span><br><span class="line"> Class&lt;?&gt; targetType = metaDataSource.getSetterType(propertyName);</span><br><span class="line"> <span class="comment">// 按照 setter 方法的参数类型进行类型转换</span></span><br><span class="line"> <span class="keyword">if</span> (targetType == Integer.class || targetType == <span class="keyword">int</span>.class) {</span><br><span class="line"> convertedValue = Integer.valueOf(value);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (targetType == Long.class || targetType == <span class="keyword">long</span>.class) {</span><br><span class="line"> convertedValue = Long.valueOf(value);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (targetType == Boolean.class || targetType == <span class="keyword">boolean</span>.class) {</span><br><span class="line"> convertedValue = Boolean.valueOf(value);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> convertedValue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> DataSource <span class="title">getDataSource</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> dataSource;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

以上是 UnpooledDataSourceFactory 的源码分析,除了 setProperties 方法稍复杂一点,其他的都比较简单,就不多说了。下面看看 PooledDataSourceFactory 的源码。

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PooledDataSourceFactory</span> <span class="keyword">extends</span> <span class="title">UnpooledDataSourceFactory</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">PooledDataSourceFactory</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// 创建 PooledDataSource</span></span><br><span class="line"> <span class="keyword">this</span>.dataSource = <span class="keyword">new</span> PooledDataSource();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

以上就是 PooledDataSource 类的所有源码,PooledDataSourceFactory 继承自 UnpooledDataSourceFactory,复用了父类的逻辑,因此它的实现很简单。

关于两种数据源的创建过程就先分析到这,接下来,我们去探究一下两种数据源是怎样实现的。

3.UnpooledDataSource

UnpooledDataSource,从名称上即可知道,该种数据源不具有池化特性。该种数据源每次会返回一个新的数据库连接,而非复用旧的连接。由于 UnpooledDataSource 无需提供连接池功能,因此它的实现非常简单。核心的方法有三个,分别如下:

  1. initializeDriver - 初始化数据库驱动
  2. doGetConnection - 获取数据连接
  3. configureConnection - 配置数据库连接

下面我将按照顺序分节对相关方法进行分析,由于 configureConnection 方法比较简单,因此我把它和 doGetConnection 放在一节中进行分析。下面先来分析 initializeDriver 方法。

3.1 初始化数据库驱动

回顾我们一开始学习使用 JDBC 访问数据库时的情景,在执行 SQL 之前,通常都是先获取数据库连接。一般步骤都是加载数据库驱动,然后通过 DriverManager 获取数据库连接。UnpooledDataSource 也是使用 JDBC 访问数据库的,因此它获取数据库连接的过程也大致如此,只不过会稍有不同。下面我们一起来看一下。

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// -☆- UnpooledDataSource</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">initializeDriver</span><span class="params">()</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="comment">// 检测缓存中是否包含了与 driver 对应的驱动实例</span></span><br><span class="line"> <span class="keyword">if</span> (!registeredDrivers.containsKey(driver)) {</span><br><span class="line"> Class&lt;?&gt; driverType;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 加载驱动类型</span></span><br><span class="line"> <span class="keyword">if</span> (driverClassLoader != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 使用 driverClassLoader 加载驱动</span></span><br><span class="line"> driverType = Class.forName(driver, <span class="keyword">true</span>, driverClassLoader);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 通过其他 ClassLoader 加载驱动</span></span><br><span class="line"> driverType = Resources.classForName(driver);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 通过反射创建驱动实例</span></span><br><span class="line"> Driver driverInstance = (Driver) driverType.newInstance();</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 注册驱动,注意这里是将 Driver 代理类 DriverProxy 对象注册到 DriverManager 中的,</span></span><br><span class="line"><span class="comment"> * 而非 Driver 对象本身。DriverProxy 中并没什么特别的逻辑,就不分析。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> DriverManager.registerDriver(<span class="keyword">new</span> DriverProxy(driverInstance));</span><br><span class="line"> <span class="comment">// 缓存驱动类名和实例</span></span><br><span class="line"> registeredDrivers.put(driver, driverInstance);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SQLException(<span class="string">"Error setting driver on UnpooledDataSource. Cause: "</span> + e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

如上,initializeDriver 方法主要包含三步操作,分别如下:

  1. 加载驱动
  2. 通过反射创建驱动实例
  3. 注册驱动实例

这三步都是都是常规操作,比较容易理解。上面代码中出现了缓存相关的逻辑,这个是用于避免重复注册驱动。因为 initializeDriver 放阿飞并不是在 UnpooledDataSource 初始化时被调用的,而是在获取数据库连接时被调用的。因此这里需要做个检测,避免每次获取数据库连接时都重新注册驱动。这个是一个比较小的点,大家看代码时注意一下即可。下面看一下获取数据库连接的逻辑。

3.2 获取数据库连接

在使用 JDBC 时,我们都是通过 DriverManager 的接口方法获取数据库连接。本节所要分析的源码也不例外,一起看一下吧。

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// -☆- UnpooledDataSource</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Connection <span class="title">getConnection</span><span class="params">()</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">return</span> doGetConnection(username, password);</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="keyword">private</span> Connection <span class="title">doGetConnection</span><span class="params">(String username, String password)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> Properties props = <span class="keyword">new</span> Properties();</span><br><span class="line"> <span class="keyword">if</span> (driverProperties != <span class="keyword">null</span>) {</span><br><span class="line"> props.putAll(driverProperties);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (username != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 存储 user 配置</span></span><br><span class="line"> props.setProperty(<span class="string">"user"</span>, username);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (password != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 存储 password 配置</span></span><br><span class="line"> props.setProperty(<span class="string">"password"</span>, password);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 调用重载方法</span></span><br><span class="line"> <span class="keyword">return</span> doGetConnection(props);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> Connection <span class="title">doGetConnection</span><span class="params">(Properties properties)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="comment">// 初始化驱动</span></span><br><span class="line"> initializeDriver();</span><br><span class="line"> <span class="comment">// 获取连接</span></span><br><span class="line"> Connection connection = DriverManager.getConnection(url, properties);</span><br><span class="line"> <span class="comment">// 配置连接,包括自动提交以及事务等级</span></span><br><span class="line"> configureConnection(connection);</span><br><span class="line"> <span class="keyword">return</span> connection;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">configureConnection</span><span class="params">(Connection conn)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">if</span> (autoCommit != <span class="keyword">null</span> &amp;&amp; autoCommit != conn.getAutoCommit()) {</span><br><span class="line"> <span class="comment">// 设置自动提交</span></span><br><span class="line"> conn.setAutoCommit(autoCommit);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (defaultTransactionIsolationLevel != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 设置事务隔离级别</span></span><br><span class="line"> conn.setTransactionIsolation(defaultTransactionIsolationLevel);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

如上,上面方法将一些配置信息放入到 Properties 对象中,然后将数据库连接和 Properties 对象传给 DriverManager 的 getConnection 方法即可获取到数据库连接。

好了,关于 UnpooledDataSource 就先说到这。下面分析一下 PooledDataSource,它的实现要复杂一些。

4.PooledDataSource

PooledDataSource 内部实现了连接池功能,用于复用数据库连接。因此,从效率上来说,PooledDataSource 要高于 UnpooledDataSource。PooledDataSource 需要借助一些辅助类帮助它完成连接池的功能,所以接下来,我们先来认识一下相关的辅助类。

4.1 辅助类介绍

PooledDataSource 需要借助两个辅助类帮其完成功能,这两个辅助类分别是 PoolState 和 PooledConnection。PoolState 用于记录连接池运行时的状态,比如连接获取次数,无效连接数量等。同时 PoolState 内部定义了两个 PooledConnection 集合,用于存储空闲连接和活跃连接。PooledConnection 内部定义了一个 Connection 类型的变量,用于指向真实的数据库连接。以及一个 Connection 的代理类,用于对部分方法调用进行拦截。至于为什么要拦截,随后将进行分析。除此之外,PooledConnection 内部也定义了一些字段,用于记录数据库连接的一些运行时状态。接下来,我们来看一下 PooledConnection 的定义。

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PooledConnection</span> <span class="keyword">implements</span> <span class="title">InvocationHandler</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String CLOSE = <span class="string">"close"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Class&lt;?&gt;[] IFACES = <span class="keyword">new</span> Class&lt;?&gt;[]{Connection.class};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> hashCode;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> PooledDataSource dataSource;</span><br><span class="line"> <span class="comment">// 真实的数据库连接</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Connection realConnection;</span><br><span class="line"> <span class="comment">// 数据库连接代理</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Connection proxyConnection;</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 从连接池中取出连接时的时间戳</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">long</span> checkoutTimestamp;</span><br><span class="line"> <span class="comment">// 数据库连接创建时间</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">long</span> createdTimestamp;</span><br><span class="line"> <span class="comment">// 数据库连接最后使用时间</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">long</span> lastUsedTimestamp;</span><br><span class="line"> <span class="comment">// connectionTypeCode = (url + username + password).hashCode()</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> connectionTypeCode;</span><br><span class="line"> <span class="comment">// 表示连接是否有效</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> valid;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">PooledConnection</span><span class="params">(Connection connection, PooledDataSource dataSource)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.hashCode = connection.hashCode();</span><br><span class="line"> <span class="keyword">this</span>.realConnection = connection;</span><br><span class="line"> <span class="keyword">this</span>.dataSource = dataSource;</span><br><span class="line"> <span class="keyword">this</span>.createdTimestamp = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">this</span>.lastUsedTimestamp = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">this</span>.valid = <span class="keyword">true</span>;</span><br><span class="line"> <span class="comment">// 创建 Connection 的代理类对象</span></span><br><span class="line"> <span class="keyword">this</span>.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, <span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{...}</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 省略部分代码</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

下面再来看看 PoolState 的定义。

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PoolState</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">protected</span> PooledDataSource dataSource;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 空闲连接列表</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">final</span> List&lt;PooledConnection&gt; idleConnections = <span class="keyword">new</span> ArrayList&lt;PooledConnection&gt;();</span><br><span class="line"> <span class="comment">// 活跃连接列表</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">final</span> List&lt;PooledConnection&gt; activeConnections = <span class="keyword">new</span> ArrayList&lt;PooledConnection&gt;();</span><br><span class="line"> <span class="comment">// 从连接池中获取连接的次数</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">long</span> requestCount = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 请求连接总耗时(单位:毫秒)</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">long</span> accumulatedRequestTime = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 连接执行时间总耗时</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">long</span> accumulatedCheckoutTime = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 执行时间超时的连接数</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">long</span> claimedOverdueConnectionCount = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 超时时间累加值</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">long</span> accumulatedCheckoutTimeOfOverdueConnections = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 等待时间累加值</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">long</span> accumulatedWaitTime = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 等待次数</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">long</span> hadToWaitCount = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 无效连接数</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">long</span> badConnectionCount = <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

上面对 PooledConnection 和 PoolState 的定义进行了一些注释,这两个类中有很多字段用来记录运行时状态。但在这些字段并非核心,因此大家知道每个字段的用途就行了。关于这两个辅助类的介绍就先到这

4.2 获取连接

前面已经说过,PooledDataSource 会将用过的连接进行回收,以便可以复用连接。因此从 PooledDataSource 获取连接时,如果空闲链接列表里有连接时,可直接取用。那如果没有空闲连接怎么办呢?此时有两种解决办法,要么创建新连接,要么等待其他连接完成任务。具体怎么做,需视情况而定。下面我们深入到源码中一探究竟。

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Connection <span class="title">getConnection</span><span class="params">()</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="comment">// 返回 Connection 的代理对象</span></span><br><span class="line"> <span class="keyword">return</span> popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> PooledConnection <span class="title">popConnection</span><span class="params">(String username, String password)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">boolean</span> countedWait = <span class="keyword">false</span>;</span><br><span class="line"> PooledConnection conn = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">long</span> t = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">int</span> localBadConnectionCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (conn == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">synchronized</span> (state) {</span><br><span class="line"> <span class="comment">// 检测空闲连接集合(idleConnections)是否为空</span></span><br><span class="line"> <span class="keyword">if</span> (!state.idleConnections.isEmpty()) {</span><br><span class="line"> <span class="comment">// idleConnections 不为空,表示有空闲连接可以使用</span></span><br><span class="line"> conn = state.idleConnections.remove(<span class="number">0</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 暂无空闲连接可用,但如果活跃连接数还未超出限制</span></span><br><span class="line"><span class="comment"> *(poolMaximumActiveConnections),则可创建新的连接</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> (state.activeConnections.size() &lt; poolMaximumActiveConnections) {</span><br><span class="line"> <span class="comment">// 创建新连接</span></span><br><span class="line"> conn = <span class="keyword">new</span> PooledConnection(dataSource.getConnection(), <span class="keyword">this</span>);</span><br><span class="line"> </span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">// 连接池已满,不能创建新连接</span></span><br><span class="line"> <span class="comment">// 取出运行时间最长的连接</span></span><br><span class="line"> PooledConnection oldestActiveConnection = state.activeConnections.get(<span class="number">0</span>);</span><br><span class="line"> <span class="comment">// 获取运行时长</span></span><br><span class="line"> <span class="keyword">long</span> longestCheckoutTime = oldestActiveConnection.getCheckoutTime();</span><br><span class="line"> <span class="comment">// 检测运行时长是否超出限制,即超时</span></span><br><span class="line"> <span class="keyword">if</span> (longestCheckoutTime &gt; poolMaximumCheckoutTime) {</span><br><span class="line"> <span class="comment">// 累加超时相关的统计字段</span></span><br><span class="line"> state.claimedOverdueConnectionCount++;</span><br><span class="line"> state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;</span><br><span class="line"> state.accumulatedCheckoutTime += longestCheckoutTime;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从活跃连接集合中移除超时连接</span></span><br><span class="line"> state.activeConnections.remove(oldestActiveConnection);</span><br><span class="line"> <span class="comment">// 若连接未设置自动提交,此处进行回滚操作</span></span><br><span class="line"> <span class="keyword">if</span> (!oldestActiveConnection.getRealConnection().getAutoCommit()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> oldestActiveConnection.getRealConnection().rollback();</span><br><span class="line"> } <span class="keyword">catch</span> (SQLException e) {...}</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 创建一个新的 PooledConnection,注意,</span></span><br><span class="line"><span class="comment"> * 此处复用 oldestActiveConnection 的 realConnection 变量</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> conn = <span class="keyword">new</span> PooledConnection(oldestActiveConnection.getRealConnection(), <span class="keyword">this</span>);</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 复用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的 </span></span><br><span class="line"><span class="comment"> * createdTimestamp 用于记录 Connection 的创建时间,而非 PooledConnection </span></span><br><span class="line"><span class="comment"> * 的创建时间。所以这里要复用原连接的时间信息。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());</span><br><span class="line"> conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 设置连接为无效状态</span></span><br><span class="line"> oldestActiveConnection.invalidate();</span><br><span class="line"> </span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">// 运行时间最长的连接并未超时</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (!countedWait) {</span><br><span class="line"> state.hadToWaitCount++;</span><br><span class="line"> countedWait = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">long</span> wt = System.currentTimeMillis();</span><br><span class="line"> <span class="comment">// 当前线程进入等待状态</span></span><br><span class="line"> state.wait(poolTimeToWait);</span><br><span class="line"> state.accumulatedWaitTime += System.currentTimeMillis() - wt;</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (conn != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 检测连接是否有效,isValid 方法除了会检测 valid 是否为 true,</span></span><br><span class="line"><span class="comment"> * 还会通过 PooledConnection 的 pingConnection 方法执行 SQL 语句,</span></span><br><span class="line"><span class="comment"> * 检测连接是否可用。pingConnection 方法的逻辑不复杂,大家可以自行分析。</span></span><br><span class="line"><span class="comment"> * 另外,官方文档在介绍 POOLED 类型数据源时,也介绍了连接有效性检测方面的</span></span><br><span class="line"><span class="comment"> * 属性,有三个:poolPingQuery,poolPingEnabled 和 </span></span><br><span class="line"><span class="comment"> * poolPingConnectionsNotUsedFor。关于这三个属性,大家可以查阅官方文档</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> (conn.isValid()) {</span><br><span class="line"> <span class="keyword">if</span> (!conn.getRealConnection().getAutoCommit()) {</span><br><span class="line"> <span class="comment">// 进行回滚操作</span></span><br><span class="line"> conn.getRealConnection().rollback();</span><br><span class="line"> }</span><br><span class="line"> conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));</span><br><span class="line"> <span class="comment">// 设置统计字段</span></span><br><span class="line"> conn.setCheckoutTimestamp(System.currentTimeMillis());</span><br><span class="line"> conn.setLastUsedTimestamp(System.currentTimeMillis());</span><br><span class="line"> state.activeConnections.add(conn);</span><br><span class="line"> state.requestCount++;</span><br><span class="line"> state.accumulatedRequestTime += System.currentTimeMillis() - t;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 连接无效,此时累加无效连接相关的统计字段</span></span><br><span class="line"> state.badConnectionCount++;</span><br><span class="line"> localBadConnectionCount++;</span><br><span class="line"> conn = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (localBadConnectionCount &gt; (poolMaximumIdleConnections</span><br><span class="line"> + poolMaximumLocalBadConnectionTolerance)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SQLException(...);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (conn == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> SQLException(...);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> conn;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

上面代码冗长,过程比较复杂,下面把代码逻辑梳理一下。从连接池中获取连接首先会遇到两种情况:

  1. 连接池中有空闲连接
  2. 连接池中无空闲连接

对于第一种情况,处理措施就很简单了,把连接取出返回即可。对于第二种情况,则要进行细分,会有如下的情况。

  1. 活跃连接数没有超出最大活跃连接数
  2. 活跃连接数超出最大活跃连接数

对于上面两种情况,第一种情况比较好处理,直接创建新的连接即可。至于第二种情况,需要再次进行细分。

  1. 活跃连接的运行时间超出限制,即超时了
  2. 活跃连接未超时

对于第一种情况,我们直接将超时连接强行中断,并进行回滚,然后复用部分字段重新创建 PooledConnection 即可。对于第二种情况,目前没有更好的处理方式了,只能等待了。下面用一段伪代码演示各种情况及相应的处理措施,如下:

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (连接池中有空闲连接) {</span><br><span class="line"> <span class="number">1</span>. 将连接从空闲连接集合中移除</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (活跃连接数未超出限制) {</span><br><span class="line"> <span class="number">1</span>. 创建新连接</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="number">1</span>. 从活跃连接集合中取出第一个元素</span><br><span class="line"> <span class="number">2</span>. 获取连接运行时长</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (连接超时) {</span><br><span class="line"> <span class="number">1</span>. 将连接从活跃集合中移除</span><br><span class="line"> <span class="number">2</span>. 复用原连接的成员变量,并创建新的 PooledConnection 对象</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="number">1</span>. 线程进入等待状态</span><br><span class="line"> <span class="number">2</span>. 线程被唤醒后,重新执行以上逻辑</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="number">1</span>. 将连接添加到活跃连接集合中</span><br><span class="line"><span class="number">2</span>. 返回连接</span><br></pre></td></tr></tbody></table>

最后用一个流程图大致描绘 popConnection 的逻辑,如下:

4.3 回收连接

相比于获取连接,回收连接的逻辑要简单的多。回收连接成功与否只取决于空闲连接集合的状态,所需处理情况很少,因此比较简单。下面看一下相关的逻辑。

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">pushConnection</span><span class="params">(PooledConnection conn)</span> <span class="keyword">throws</span> SQLException </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (state) {</span><br><span class="line"> <span class="comment">// 从活跃连接池中移除连接</span></span><br><span class="line"> state.activeConnections.remove(conn);</span><br><span class="line"> <span class="keyword">if</span> (conn.isValid()) {</span><br><span class="line"> <span class="comment">// 空闲连接集合未满</span></span><br><span class="line"> <span class="keyword">if</span> (state.idleConnections.size() &lt; poolMaximumIdleConnections</span><br><span class="line"> &amp;&amp; conn.getConnectionTypeCode() == expectedConnectionTypeCode) {</span><br><span class="line"> state.accumulatedCheckoutTime += conn.getCheckoutTime();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 回滚未提交的事务</span></span><br><span class="line"> <span class="keyword">if</span> (!conn.getRealConnection().getAutoCommit()) {</span><br><span class="line"> conn.getRealConnection().rollback();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建新的 PooledConnection</span></span><br><span class="line"> PooledConnection newConn = <span class="keyword">new</span> PooledConnection(conn.getRealConnection(), <span class="keyword">this</span>);</span><br><span class="line"> state.idleConnections.add(newConn);</span><br><span class="line"> <span class="comment">// 复用时间信息</span></span><br><span class="line"> newConn.setCreatedTimestamp(conn.getCreatedTimestamp());</span><br><span class="line"> newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将原连接置为无效状态</span></span><br><span class="line"> conn.invalidate();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 通知等待的线程</span></span><br><span class="line"> state.notifyAll();</span><br><span class="line"> </span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">// 空闲连接集合已满</span></span><br><span class="line"> state.accumulatedCheckoutTime += conn.getCheckoutTime();</span><br><span class="line"> <span class="comment">// 回滚未提交的事务</span></span><br><span class="line"> <span class="keyword">if</span> (!conn.getRealConnection().getAutoCommit()) {</span><br><span class="line"> conn.getRealConnection().rollback();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 关闭数据库连接</span></span><br><span class="line"> conn.getRealConnection().close();</span><br><span class="line"> conn.invalidate();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> state.badConnectionCount++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

上面代码首先将连接从活跃连接集合中移除,然后再根据空闲集合是否有空闲空间进行后续处理。如果空闲集合未满,此时复用原连接的字段信息创建新的连接,并将其放入空闲集合中即可。若空闲集合已满,此时无需回收连接,直接关闭即可。pushConnection 方法的逻辑并不复杂,就不多说了。

我们知道获取连接的方法 popConnection 是由 getConnection 方法调用的,那回收连接的方法 pushConnection 是由谁调用的呢?答案是 PooledConnection 中的代理逻辑。相关代码如下:

<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// -☆- PooledConnection</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> String methodName = method.getName();</span><br><span class="line"> <span class="comment">// 检测 close 方法是否被调用,若被调用则拦截之</span></span><br><span class="line"> <span class="keyword">if</span> (CLOSE.hashCode() == methodName.hashCode() &amp;&amp; CLOSE.equals(methodName)) {</span><br><span class="line"> <span class="comment">// 将回收连接中,而不是直接将连接关闭</span></span><br><span class="line"> dataSource.pushConnection(<span class="keyword">this</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (!Object.class.equals(method.getDeclaringClass())) {</span><br><span class="line"> checkConnection();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 调用真实连接的目标方法</span></span><br><span class="line"> <span class="keyword">return</span> method.invoke(realConnection, args);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> <span class="keyword">throw</span> ExceptionUtil.unwrapThrowable(t);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>

在上一节中,getConnection 方法返回的是 Connection 代理对象,不知道大家有没有注意到。代理对象中的方法被调用时,会被上面的代理逻辑所拦截。如果代理对象的 close 方法被调用,MyBatis 并不会直接调用真实连接的 close 方法关闭连接,而是调用 pushConnection 方法回收连接。同时会唤醒处于睡眠中的线程,使其恢复运行。整个过程并不复杂,就不多说了。

4.4 小节

本章分析了 PooledDataSource 的部分源码及一些辅助类的源码,除此之外,PooledDataSource 中还有部分源码没有分析,大家若有兴趣,可自行分析。好了,关于 PooledDataSource 的分析就先到这。

5.总结

本篇文章对 MyBatis 两种内置数据源进行了较为详细的分析,总的来说,这两种数据源的源码都不是很难理解。大家在阅读源码的过程中,首先应搞懂源码的主要逻辑,然后再去分析一些边边角角的逻辑。不要一开始就陷入各种细节中,容易迷失方向。

好了,到此本文就结束了。若文章有错误不妥之处,希望大家指明。最后,感谢大家阅读我的文章。

附录:MyBatis 源码分析系列文章列表

田小波

勿在浮沙筑高台

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

MyBatis 源码分析 - 内置数据源 - HelloWorld