本篇来介绍一下 mybatis 的数据源连接池相关内容,我们平时在项目中使用的数据源连接池有Hikaricp、Druid、c3p0等等,大多数情况下我们使用 mybatis 的时候都是集成外部数据源连接池来使用,其实 mybatis 自身也实现了简单的数据源连接池。
知识点
- 什么是连接池
- 如何使用 mybatis 连接池
- mybatis 连接池的实现原理
什么是连接池
关于连接池的概念,我相信大家都比较清楚,对于很新很新的新人,这里还是简单的介绍一下。简单的说就是一个容器里放了很多个连接,我们需要连接就去容器里拿,用完了就还回去,容器负责对所有连接的管理。画了个图,可以感受一下:
如果还不能理解,就把连接池想象成一个外包公司,里面有很多员工,外面有项目需要就派人出去,项目结束了人就回来,公司就是一个池子,员工就是每个连接。
如何使用 mybatis 连接池
mybatis原生方式
对于 mybatis 连接池的使用,官方文档有介绍
在 dataSource 中指定 type 即可,目前支持三种内置的实现,分别为 UNPOOLED、POOLED、JNDI,这三种都是 mybatis 的内置定义的假名,会去找到实际的类型,当然我们也可以自定义数据源工厂类来指定其他的数据源,具体实现可以参考官网。
集成 spring 方式
通常我们都是集成 srping 来使用 mybatis 的,集成了 spring 之后,以上方式就不起作用了,原因是 spring 框架中会去替换配置里的环境信息(也就是 environment 节点),此时我们就得通过 spring 的配置方式来指定数据源,大致需要以下两步:
1、配置数据源必填属性
2、指定 spring 数据源
使用 springboot 方式
随着 springboot 使用地越来越多,mybatis 对应的 srpingboot 集成包也面世了,项目中引入以下包即可使用
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
当然 mybatis 的 springboot 方式在配置方面还有很多局限性,也包括我们这次要说的使用 mybatis 自带的连接池。如果要使用 mybatis 自带的连接池,一定要自定义一个连接池类来进行包装,也是两步:
1、自定义一个连接池包装类
package com.example.mybatisanalyze.datasource;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* @author Don
* @date 2022/2/7.
*/
public class MyDataSource implements DataSource {
private final PooledDataSource dataSource = new PooledDataSource();
public void setDriverClassName(String driver) {
dataSource.setDriver(driver);
}
public void setUrl(String url) {
dataSource.setUrl(url);
}
public void setUsername(String username) {
dataSource.setUsername(username);
}
public void setPassword(String password) {
dataSource.setPassword(password);
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return dataSource.getConnection();
}
@Override
public T unwrap(Class iface) throws SQLException {
return dataSource.unwrap(iface);
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return dataSource.isWrapperFor(iface);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return dataSource.getLogWriter();
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
dataSource.setLogWriter(out);
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
dataSource.setLoginTimeout(seconds);
}
@Override
public int getLoginTimeout() throws SQLException {
return dataSource.getLoginTimeout();
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return dataSource.getParentLogger();
}
}
2、配置文件中指定数据源
spring.datasource.type=com.example.mybatisanalyze.datasource.MyDataSource
spring.datasource.username=${username}
spring.datasource.password=${password}
spring.datasource.url=${url}
spring.datasource.driver-class-name=${driverclass}
为什么不能直接指定数据源类型为org.apache.ibatis.datasource.pooled.PooledDataSource
呢,仔细看代码的朋友估计已经发现了,封装的方法有一处和实际类型的接口名称是不一致的
因为 springboot 是基于driverClassName来进行注入的,但是 mybatis 的连接池只提供了 driver 的接口注入。
mybatis 连接池的实现原理
概念和使用方面都介绍完了,最后来看一下 mybatis 的连接池是如何实现的,先看代码
mybatis 连接池相关的代码都在这个包下面。从这里就可以看出有三种实现:jndi、pooled、unpooled,和官方介绍的配置相符。同时也能看出它使用的是工厂模式来创建数据源。jndi 就不做具体介绍了,大概就是通过配置文件的方式进行配置,然后在代码中可以根据名称取得具体的数据源对象进行使用,tomcat就是这么做的,参考JNDI介绍。主要来看下 pooled 方式,顺带也会了解 unpooled 方式。
在介绍原生方式使用的时候说过,通过配置 type 成 POOLED,我们就可以使用池化的数据源,首先是因为 mybatis 内置了对应的假名和具体实现类的映射
这样就找到具体实现类PooledDataSourceFactory
,看下具体代码
可以看到继承了UnpooledDataSourceFactory
,主要是为了对配置的属性进行赋值
另外就是生成了一个PooledDataSource
。这是池化的数据源对象,我这里把注释加了一下,方便理解
可以看到其中有一个UnpooledDataSource
类型的变量,它就是负责新连接创建的。
所有的用户名密码等属性都是在这里,包括数据库驱动包也是在这里加载,说白了这个类就是配置数据库信息以及获取新连接用的。再回过头来看下PooledDataSource
,我们去获取连接的时候,它会进行一次 pop 操作
这个pop操作是线程安全的,因为用了一个锁,这个锁对象就是PoolState
,PoolState
是整个池子下唯一的,它用于对连接池信息进行管理,管理哪些信息呢,我这里也在代码中做了注释
这样就很清晰了,有了PoolState
,我们就能够更好的了解连接池的使用情况。再次回过头看PooledDataSource
的 popConnection 方法
这里分解出来就是以下逻辑:
1、空闲连接存在,则直接检出;
2、空闲连接不存在,则先判断活跃连接是否达到最大活跃数
1)否,则新建连接
2)是,则取出最早检出的活跃连接,判断检出时间是否大于最大逾期时间
- 是,则声明该连接失效,并新建连接
- 否,则等待配置的等待时间(2s)
3、取到连接则进行一次统计并返回,取不到则再次循环获取
再来看一下 pushConnection 方法
顾名思义,pop是取出连接,push就是把连接放回去,和pop用的一把锁,同样是线程安全的。什么时候触发连接放回操作呢?看下这个类PooledConnection
我在这里同样加了注释,这个类的作用大概就是对真实连接的代理,而整个代理操作就是对于close操作放回连接到连接池
在每次 pop 和 push 连接的时候会检测一下连接的有效性,这时候就用到了 pingConnection 方法
这个方法也比较简单,就是周期到了会去检测一次连接有效性。
总结
连接池这一块实现还是比较简单的,我们自己在开发设计的时候也可以参考它是怎么实现的,有哪些好处,对于其他连接池也是异曲同工。