Mybatis源码阅读之六——数据库连接池与hikariCP简析

【系列目录】
Mybatis源码阅读之一——工厂模式与SqlSessionFactory

Mybatis源码阅读之二——模板方法模式与Executor

Mybatis源码阅读之三——JDBC解析与Mybatis封装

Mybatis源码阅读之四——装饰器模式与Mybatis中的各种Cache
Mybatis源码阅读之六——数据库连接池实现与hikariCP简析

【本文目录】


前文 Mybatis源码阅读之五——Java的XML解析我们介绍了Mybatis如何解析XML,本文来看一下它自身的数据源连接池实现,以及与其他热门连接池的比较。

数据库连接池

池化技术在JAVA中有很多应用,比如典型的线程池,ThreadPoolExecutor是必知必会。
数据库连接池也是一样的,Mybatis每次发起一个请求都需要与数据库服务器建立一个新的网络连接,这样的消耗显然不合理,于是数据库连接池应运而生。

  • 数据库连接池当需要使用连接时,从连接池中获取,当使用完连接后,连接不会直接销毁,而是放回连接池。
  • 优势:
    • 连接复用
    • 连接总数量可控

如何实现一个数据库连接池

在之前Mybatis源码阅读之三——JDBC解析与Mybatis封装中我们已经介绍了JDBC以及数据库连接Connection。我们要做的,就是将Connection封装成一个池。

  • 第一步,定义PoolConnectionFactory,一个连接的获取/归还方法,用以替代java.sql.DriveManager。
import java.sql.Connection;

public interface PoolConnectionFactory{


  // 获取连接
  Connection getConnection() throws SQLException;
  // 归还连接
  void reserveConnection(Connection conn);
}

  • 第二步,定义池结构,简单一些,直接用一个ArrayList
    • 一个空闲连接池用于存放没有被使用的连接
    • 一个活跃连接池用于存放正在被使用的连接
    • 一个用于控制连接池大小的size
    • 额外的,还可以定义连接池满的拒绝策略,排队阻塞队列等。
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

public class PoolConnectionFactoryImpl implements PoolConnectionFactory {

  final private  List<Connection> idleList =  new ArrayList<>();
  final private  List<Connection> activeList =  new ArrayList<>();
  private int MAX_POOL_SIZE = 0;


  public PoolConnectionFactoryImpl(int MAX_POOL_SIZE) {
    this.MAX_POOL_SIZE = MAX_POOL_SIZE;
  }
  
  @Override
  public Connection getConnection() {
  // TODO
    return null;
  }

  @Override
  public void reserveConnection(Connection conn) {
  // TODO
    return null;
  }
}
  • 第三步,实现连接的获取方法getConnection,归还连接的reserveConnection。简单展开下getCnnection。
    • 上锁,连接的获取/归还是并发操作需要上锁保证逻辑正确,这里有更好的实现方法,见下文hikariCP
    • 如果空闲连接池还有连接,直接使用,否则下一步。
    • 如果活跃连接池+空闲连接池的size没有达到最大限度,那么生成新的连接。
    • 如果已经达到最大限度,那么拒绝(或设计其他拒绝策略,如kill掉耗时最久的连接,将连接给本次请求使用)
@Override
  public Connection getConnection() throws SQLException {
    Connection conn = null;
    synchronized (this){
      if(!idleList.isEmpty()){
        conn = idleList.get(0);
        idleList.remove(conn);
        activeList.add(conn);
      }else{
        if(activeList.size()+idleList.size()<MAX_POOL_SIZE){
          conn = DriverManager.getConnection("mock", "mock", "mock");
          activeList.add(conn);
        } else{
          // TODO 可以设计拒绝策略
          throw new RejectedExecutionException("连接池繁忙,无连接可使用");
        }
      }
    }
  return conn;
  }

  @Override
  public void reserveConnection(Connection conn) {
    synchronized (this){
      idleList.add(conn);
      activeList.remove(conn);
    }
  }

如上只是一个demo级别的实现,不建议生产使用,那么让我们看一下,Mybatis官方以及其他优秀的数据库连接池框架是怎么实现的。

Mybatis连接池实现

非池化与池化

  • 包结构,我们关注pooled(池化连接管理),unpooled(非池化连接管理)
  • DataSourceFactory用于定义获取数据源的接口
    工厂的具体实现类只是通过配置获取到url等信息。

  • DataSource接口是jdbc包下。
  • unpooled (主要逻辑在UnpooledDataSource.getConnection)
    最终,也是通过DriverManager获取连接的,没有额外封装处理。
    请添加图片描述
  • pooled,重点来了——PooledDataSource变量定义,值得关注的一个是UnpooledDataSource,即池化的数据源只是在非池化上面做了封装。另外一点,PoolState实际上就是连接池,囊括了基本的空闲池与活跃池。
  • PooledDataSource.getConnection()



    • 第一步,死循环获取连接。对池上锁,保证并发正确性。
    • 第二步,若空闲池有存量则使用,否则第三步。
    • 第三步,若活跃池未超过活跃池最大限制,则新建一个连接,否则第四步。
    • 第四步,拒绝策略,如果活跃池中耗时最久的连接超过了最大任务执行时间,则会被停止回滚,并将连接拿过来使用。
    • 第五步,时间最长的连接也没有超时,那么新任务只能等待。

扩展

Mybatis虽然自己实现了一套连接池,但是秉持着开源社区的理念,它提供了数据源连接池的扩展方式,具体执行的地方正是在我们前文Mybatis源码阅读之五——Java的XML解析提到的mybatis-config解析过程中。


也即,如果我们想要让mybatis使用我们自定义的连接池,那么就需要实现一个DataSourceFactory/DataSource,同时设置一下mybatis的datasource.type配置。

其他连接池比较

Mybatis实现的连接池,最火热的有HikariCP连接池,Durid连接池,这两个连接池,一个是以快为闻名,另一个则是以监控出名。
HikariCP 的hikari,取自日语中“光”的翻译,一个字,快。
Durid的官方描述则是:阿里云计算平台DataWorks团队出品,为监控而生的数据库连接池。
监控的功能实际上其他开源组建也可以实现,所以对比而言,我个人还是对HikariCP更感兴趣。

HikariCP

为什么说HikariCP快?它比一般的数据库连接池强在哪里?

  • 字节码级别优化,很多方法通过JavaAssist生成。(JavaAssist字节码生成技术相较于JDK和CGLIB,没有去继承/包装一些额外的类,精简了许多)
  • 使用FastList代替ArrayList,以及使用CopyOnWriteArrayList等大量并发方面等优化
  • 使用ConcurrentBag的设计

ConcurrentBag

其中,第三点与连接池的设计有关,我们简单了解下它的实现。
在一个完善的数据库连接池中,我们需要在四个方法考虑并发问题:

  • add,添加一个连接。
  • remove,移除一个连接。
  • get/borrow,获取一个连接。
  • return/reserve,归还一个连接。

在我们之前的设计中,会存在一个活跃池和一个空闲池,用于区分这个连接是否被使用,这样就导致当get/return一个连接时,需要对这两个池同时上锁才能完成,而get/return又是高频方法,如果能够避免在此时进行锁操作,性能就一定能得到提升。
hikariCP就是这么做的,它使用了无锁集合ConcurrentBag。

ConcurrentBag没有区分活跃池和空闲池,而是一个统一的集合,区分是否被使用则是通过一个标识,有同学可能有疑问了,并发情况下,不还是可能会同时被多个线程拿到一个连接吗?
怎么解决?CAS操作标识位。

篇幅有限,后面抽时间再来专门分析一下hikariCP的源码。


欢迎关注微信公众号 【JAVA技术分享官】,公众号首发,持续输出原创高质量JAVA开发者知识点

MyBatis是一个持久层框架,它并不直接提供数据库连接池的功能,而是依赖于其他的数据库连接池实现来管理数据库连接。 在MyBatis中,可以通过配置文件来指定使用的数据库连接池。常用的数据库连接池实现有: 1. Apache Commons DBCP:一个开源的数据库连接池实现,具有良好的性能和稳定性。 2. C3P0:另一个常用的开源数据库连接池实现,提供了更多的配置选项和监控功能。 3. HikariCP:一个高性能的数据库连接池实现,相对于DBCP和C3P0更为轻量级和快速。 你可以根据自己的需求选择适合的数据库连接池实现,并在MyBatis的配置文件中进行相关配置。一般情况下,你需要配置连接池的最大连接数、最小连接数、连接超时时间等参数。 例如,使用Apache Commons DBCP作为数据库连接池的示例配置如下: ```xml <dataSource type="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/> <property name="username" value="root"/> <property name="password" value="mypassword"/> <property name="initialSize" value="5"/> <property name="maxTotal" value="20"/> <property name="maxIdle" value="10"/> <property name="maxWaitMillis" value="10000"/> </dataSource> ``` 这是一个简单的配置示例,你可以根据自己的实际情况进行调整。在配置文件中,还可以设置其他属性,如连接池的验证语句、连接池的空闲对象清理策略等。 需要注意的是,MyBatis只是使用数据库连接池来管理数据库连接,并不负责连接池的具体实现。因此,你需要在项目中引入相应的数据库连接池实现,并将其配置到MyBatis的配置文件中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值