MyBatis 源码分析 - 内置数据源

蜂信物联FastBee平台https://gitee.com/beecue/fastbee

阿里资料开源项目https://gitee.com/vip204888

百度低代码前端框架https://gitee.com/baidu/amis

OpenHarmony开源项目https://gitcode.com/openharmony

仓颉编程语言开放项目https://gitcode.com/Cangjie
// 存储 user 配置

props.setProperty(“user”, username);

}

if (password != null) {

// 存储 password 配置

props.setProperty(“password”, password);

}

// 调用重载方法

return doGetConnection(props);

}

private Connection doGetConnection(Properties properties) throws SQLException {

// 初始化驱动

initializeDriver();

// 获取连接

Connection connection = DriverManager.getConnection(url, properties);

// 配置连接,包括自动提交以及事务等级

configureConnection(connection);

return connection;

}

private void configureConnection(Connection conn) throws SQLException {

if (autoCommit != null && autoCommit != conn.getAutoCommit()) {

// 设置自动提交

conn.setAutoCommit(autoCommit);

}

if (defaultTransactionIsolationLevel != null) {

// 设置事务隔离级别

conn.setTransactionIsolation(defaultTransactionIsolationLevel);

}

}

如上,上面方法将一些配置信息放入到 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 的定义。

class PooledConnection implements InvocationHandler {

private static final String CLOSE = “close”;

private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};

private final int hashCode;

private final PooledDataSource dataSource;

// 真实的数据库连接

private final Connection realConnection;

// 数据库连接代理

private final Connection proxyConnection;

// 从连接池中取出连接时的时间戳

private long checkoutTimestamp;

// 数据库连接创建时间

private long createdTimestamp;

// 数据库连接最后使用时间

private long lastUsedTimestamp;

// connectionTypeCode = (url + username + password).hashCode()

private int connectionTypeCode;

// 表示连接是否有效

private boolean valid;

public PooledConnection(Connection connection, PooledDataSource dataSource) {

this.hashCode = connection.hashCode();

this.realConnection = connection;

this.dataSource = dataSource;

this.createdTimestamp = System.currentTimeMillis();

this.lastUsedTimestamp = System.currentTimeMillis();

this.valid = true;

// 创建 Connection 的代理类对象

this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {…}

java

// 省略部分代码

}

下面再来看看 PoolState 的定义。

public class PoolState {

protected PooledDataSource dataSource;

// 空闲连接列表

protected final List idleConnections = new ArrayList();

// 活跃连接列表

protected final List activeConnections = new ArrayList();

// 从连接池中获取连接的次数

protected long requestCount = 0;

// 请求连接总耗时(单位:毫秒)

protected long accumulatedRequestTime = 0;

// 连接执行时间总耗时

protected long accumulatedCheckoutTime = 0;

// 执行时间超时的连接数

protected long claimedOverdueConnectionCount = 0;

// 超时时间累加值

protected long accumulatedCheckoutTimeOfOverdueConnections = 0;

// 等待时间累加值

protected long accumulatedWaitTime = 0;

// 等待次数

protected long hadToWaitCount = 0;

// 无效连接数

protected long badConnectionCount = 0;

}

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

4.2 获取连接

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

public Connection getConnection() throws SQLException {

// 返回 Connection 的代理对象

return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();

}

private PooledConnection popConnection(String username, String password) throws SQLException {

boolean countedWait = false;

PooledConnection conn = null;

long t = System.currentTimeMillis();

int localBadConnectionCount = 0;

while (conn == null) {

synchronized (state) {

// 检测空闲连接集合(idleConnections)是否为空

if (!state.idleConnections.isEmpty()) {

// idleConnections 不为空,表示有空闲连接可以使用

conn = state.idleConnections.remove(0);

} else {

/*

  • 暂无空闲连接可用,但如果活跃连接数还未超出限制

*(poolMaximumActiveConnections),则可创建新的连接

*/

if (state.activeConnections.size() < poolMaximumActiveConnections) {

// 创建新连接

conn = new PooledConnection(dataSource.getConnection(), this);

} else { // 连接池已满,不能创建新连接

// 取出运行时间最长的连接

PooledConnection oldestActiveConnection = state.activeConnections.get(0);

// 获取运行时长

long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();

// 检测运行时长是否超出限制,即超时

if (longestCheckoutTime > poolMaximumCheckoutTime) {

// 累加超时相关的统计字段

state.claimedOverdueConnectionCount++;

state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;

state.accumulatedCheckoutTime += longestCheckoutTime;

// 从活跃连接集合中移除超时连接

state.activeConnections.remove(oldestActiveConnection);

// 若连接未设置自动提交,此处进行回滚操作

if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {

try {

oldestActiveConnection.getRealConnection().rollback();

} catch (SQLException e) {…}

}

/*

  • 创建一个新的 PooledConnection,注意,

  • 此处复用 oldestActiveConnection 的 realConnection 变量

*/

conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);

/*

  • 复用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的

  • createdTimestamp 用于记录 Connection 的创建时间,而非 PooledConnection

  • 的创建时间。所以这里要复用原连接的时间信息。

*/

conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());

conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());

// 设置连接为无效状态

oldestActiveConnection.invalidate();

} else { // 运行时间最长的连接并未超时

try {

if (!countedWait) {

state.hadToWaitCount++;

countedWait = true;

}

long wt = System.currentTimeMillis();

// 当前线程进入等待状态

state.wait(poolTimeToWait);

state.accumulatedWaitTime += System.currentTimeMillis() - wt;

} catch (InterruptedException e) {

break;

}

}

}

}

if (conn != null) {

/*

  • 检测连接是否有效,isValid 方法除了会检测 valid 是否为 true,

  • 还会通过 PooledConnection 的 pingConnection 方法执行 SQL 语句,

  • 检测连接是否可用。pingConnection 方法的逻辑不复杂,大家可以自行分析。

  • 另外,官方文档在介绍 POOLED 类型数据源时,也介绍了连接有效性检测方面的

  • 属性,有三个:poolPingQuery,poolPingEnabled 和

  • poolPingConnectionsNotUsedFor。关于这三个属性,大家可以查阅官方文档

*/

if (conn.isValid()) {

if (!conn.getRealConnection().getAutoCommit()) {

// 进行回滚操作

conn.getRealConnection().rollback();

}

conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));

// 设置统计字段

conn.setCheckoutTimestamp(System.currentTimeMillis());

conn.setLastUsedTimestamp(System.currentTimeMillis());

state.activeConnections.add(conn);

state.requestCount++;

state.accumulatedRequestTime += System.currentTimeMillis() - t;

} else {

// 连接无效,此时累加无效连接相关的统计字段

state.badConnectionCount++;

localBadConnectionCount++;

conn = null;

if (localBadConnectionCount > (poolMaximumIdleConnections

  • poolMaximumLocalBadConnectionTolerance)) {

throw new SQLException(…);

}

}

}

}

}

if (conn == null) {

throw new SQLException(…);

}

return conn;

}

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

  1. 连接池中有空闲连接

  2. 连接池中无空闲连接

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

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

  2. 活跃连接数超出最大活跃连接数

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

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

  2. 活跃连接未超时

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

if (连接池中有空闲连接) {

  1. 将连接从空闲连接集合中移除

} else {

if (活跃连接数未超出限制) {

  1. 创建新连接

} else {

  1. 从活跃连接集合中取出第一个元素

  2. 获取连接运行时长

if (连接超时) {

  1. 将连接从活跃集合中移除

  2. 复用原连接的成员变量,并创建新的 PooledConnection 对象

} else {

  1. 线程进入等待状态

  2. 线程被唤醒后,重新执行以上逻辑

}

}

}

  1. 将连接添加到活跃连接集合中

  2. 返回连接

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

img

4.3 回收连接

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

protected void pushConnection(PooledConnection conn) throws SQLException {

synchronized (state) {

// 从活跃连接池中移除连接

state.activeConnections.remove(conn);

if (conn.isValid()) {

// 空闲连接集合未满

if (state.idleConnections.size() < poolMaximumIdleConnections

&& conn.getConnectionTypeCode() == expectedConnectionTypeCode) {

state.accumulatedCheckoutTime += conn.getCheckoutTime();

// 回滚未提交的事务

if (!conn.getRealConnection().getAutoCommit()) {

conn.getRealConnection().rollback();

}

// 创建新的 PooledConnection

PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);

state.idleConnections.add(newConn);

// 复用时间信息

newConn.setCreatedTimestamp(conn.getCreatedTimestamp());

newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());

// 将原连接置为无效状态

conn.invalidate();

// 通知等待的线程

state.notifyAll();

} else { // 空闲连接集合已满

state.accumulatedCheckoutTime += conn.getCheckoutTime();

// 回滚未提交的事务

if (!conn.getRealConnection().getAutoCommit()) {

conn.getRealConnection().rollback();

}

// 关闭数据库连接

conn.getRealConnection().close();

conn.invalidate();

}

} else {

state.badConnectionCount++;

}

}

}

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

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

Kafka实战笔记

关于这份笔记,为了不影响大家的阅读体验,我只能在文章中展示部分的章节内容和核心截图

image.png

  • Kafka入门
  • 为什么选择Kafka
  • Karka的安装、管理和配置

image.png

  • Kafka的集群
  • 第一个Kafka程序
  • image.png

afka的生产者

image.png

  • Kafka的消费者
  • 深入理解Kafka
  • 可靠的数据传递

image.png

image.png

  • Spring和Kalka的整合
  • Sprinboot和Kafka的整合
  • Kafka实战之削峰填谷
  • 数据管道和流式处理(了解即可)

image.png

  • Kafka实战之削峰填谷

image.png

ction 中的代理逻辑。相关代码如下:

Kafka实战笔记

关于这份笔记,为了不影响大家的阅读体验,我只能在文章中展示部分的章节内容和核心截图

[外链图片转存中…(img-9FmUyPC6-1725198548881)]

  • Kafka入门
  • 为什么选择Kafka
  • Karka的安装、管理和配置

[外链图片转存中…(img-c5sI8uOc-1725198548882)]

  • Kafka的集群
  • 第一个Kafka程序
  • [外链图片转存中…(img-lFwPfoHV-1725198548882)]

afka的生产者

[外链图片转存中…(img-3YKFdDBf-1725198548883)]

  • Kafka的消费者
  • 深入理解Kafka
  • 可靠的数据传递

[外链图片转存中…(img-dxx3p7Lc-1725198548884)]

[外链图片转存中…(img-ZZSeVr4h-1725198548884)]

  • Spring和Kalka的整合
  • Sprinboot和Kafka的整合
  • Kafka实战之削峰填谷
  • 数据管道和流式处理(了解即可)

[外链图片转存中…(img-MEPMw06S-1725198548885)]

  • Kafka实战之削峰填谷

[外链图片转存中…(img-LyQmLNIO-1725198548885)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值