一文读懂mybatis连接池原理

时间:2023-01-13 09:54:59

本文需要配合代码demo一起观看更佳源码地址

本源码中对 mybatis代码做了详尽的注释。对mybatis源码进行了详尽的注释,且可以对项目进行install,然后在ron-man-mybatis1项目中 src/main/java/iron/man/lyf/ironmanmybatis1/run_test/MybatisQuickStart.java 进行运行 对mybatis源码进行debug查看运行过程,欢迎大家下载指正。如果您觉得帮助到您麻烦给个赞

一、概述

  1. datasource下的两个包一个是不使用连接池、一个是使用mybatis提供的连接池

    • 连接池:src/main/java/org/apache/ibatis/datasource/pooled
    • 非连接池:src/main/java/org/apache/ibatis/datasource/unpooled
  2. 此dataSource是使用的工厂模式创建的。

    1. 抽象工厂: DataSourceFactory是抽象工厂

    2. 具体工厂:pooledunpooled两个包中的UnpooledDataSourceFactoryPooledDataSourceFactory 是抽象工厂的两个实现类。PooledDataSourceFactory继承了UnPooledDataSourceFactory。两个类基本一模一样,只不过PooledDataSourceFactory中实例化的是PooledDataSourceUnPooledDataSourceFactory中实例化的是UnPooledDataSource。所以会看到在使用mybatis默认连接池的时候,却会时常调用UnPooledDataSourceFactory的方法。实际是由于UnPooledDataSourceFactory继承了UnPooledDataSourceFactory全部方法的缘故

    3. 抽象产品:工厂要生产的对象的抽象类是jdk 的 /javax/sql/DataSource

    4. 具体产品:pooledunpooled两个包中 的UnpooledDataSource、PooledDataSource 是jdk 的 DataSource 的两个实现类

  3. 可以把DataSource 的实现类看做一个手写的jdbc连接数据库的实现类(iron-man-mybatis/iron-man-mybatis1/src/main/java/iron/man/lyf/ironmanmybatis1/run_test/JdbcDemo.java)

    1. 尤其UnpooledDataSource中,只不过是把driver、url、username、password…这些连接参数提成了成员变量,由程序从配置文件读取然后给这些变量赋值。然后就是构造器多了一点,还有一些get、set方法干扰。最后就是initializeDriver加载驱动、doGetConnection从jdk拿去数据库链接Connection,与JdbcDemo的数据库连接无异。
    2. PooledDataSource 会把 UnpooledDataSource 放到属性中,是为了重复使用UnpooledDataSource中的driver、url、username、password…这些字段。PooledDataSource会存一些 最大活跃连接数最大闲置连接数等这些固定的值,而 PoolState 属性会存程序运行起来的一些值,譬如:空闲的连接池资源集合活跃的连接池资源集合等。值得注意的是PoolState.toString()会返回当前连接池的一堆关键参数

二、执行流程(以mybatis的PooledDataSource连接池为例)

  1. 每创建一个sqlSession就会进行一次数据库连接操作。由:iron-man-mybatis/iron-man-mybatis1/src/main/java/iron/man/lyf/ironmanmybatis1/run_test/CacheTest.java#Test1LevelCache 可得知。此demo创建了两个sqlsession,进行了两次数据库连接。

  2. 在src/main/java/org/apache/ibatis/datasource/pooled/PooledDataSource.java 类中的popConnectionpushConnectionpingConnection方法点断点,结合demo:CacheTest.java#Test1LevelCache 即可看到连接池运行的全貌

  3. popConnection是负责在开启一个新的sqlsession需要一个数据库连接的时候弹出来一个数据库连接。其中还会操作idleConnections(空闲的连接池资源集合)activeConnections(活跃的连接池资源集合)等状态。

  4. pushConnection是在关闭sqlsession,需要把这个数据库连接归还连接池时候进行调用。期间也会操作idleConnections(空闲的连接池资源集合)activeConnections(活跃的连接池资源集合)等状态。

  5. pingConnection是在popConnection时需要有一个验证当前连接是否有效,如果在mybatis 的配置文件 environments属性中配置了 poolPingEnabled为true,那么就会在pingConnection进行一个数据库查询的操作,查询的sql是在environments属性中配置的 poolPingQuery 的sql。

  6. /datasource/pooled/PooledConnection.java 是用对Connection做的一个动态代理增强。

    其主要作用是:

    • 保存了 jdk给的 realConnection的这个连接。
    • 在执行 sqlsession.close时 在invoke方法中 增强,来在close前执行 pushConnection 方法。

三、popConnection执行流程(从连接池中拿去一个连接)

注意点:

  1. 第一次执行popConnection的时候空闲连接肯定为0,所以会直接创建一个连接
  2. popConnection内创建的PooledConnection只是一个外壳,真正的connection 是PooledConnection内部的 realConnection
  3. 不管是怎么得到的一个连接,都会重新设置checkoutTimestamp为当前时间,这个值在超时判断中起决定作用
  4. 当一个连接超时后,需要移除一个连接时,移除的只是PooledConnection外壳,真正的realConnection不会移除只是放到新创建的PooledConnection新的外壳中。

一文读懂mybatis连接池原理

四、pushConnection执行流程(归还连接池一个连接)

一文读懂mybatis连接池原理

五、连接池总结:

  1. 当来一个连接请求会先看连接池有没有空闲的连接,有的话直接返回空闲连接。

  2. 如果没有空闲连接。

    1. 判断当前活跃的连接数是否到了最大连接数

      1. 未达到则直接创建一个连接(此连接是从数据库拿一个真正的连接)
      2. 已达到则判断现在的活跃的连接是否有超时的连接
        1. 如果有超时连接。则把以前的事务回滚,把超时的连接扒掉旧的PooledConnection外壳,拿到真正的realConnection,创建一个新的PooledConnection,把realConnection放进去。
        2. 如果没有超时连接,那么调用wait方法阻塞等待
  3. 验证连接

  4. 如果验证通过,那么进一步初始化PooledConnection。并修改一些状态值
    2. 如果验证不通过,即连接无效。那么badConnectionCount(累计的获取无效连接次数+1)

    • 如果重试次数超过阈值,则报错。