51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

MyBatis 内置连接池原理详解!

你好,我是猿java

MyBatis 是一个流行的持久层框架,提供了一个简单且灵活的方式来访问数据库,它内置了一个连接池来管理数据库连接。这篇文章,我们将深入分析 MyBatis 内置的连接池源码,包括设计原理、类结构,以及核心方法的实现等。

  1. 连接池原理 {#1-连接池原理} ===================

MyBatis 内置的连接池采用了传统的 Java JDBC 连接方式,它负责管理数据库连接的创建、维护和销毁。连接池的设计可以避免每次请求数据库时都重新创建连接,从而提高性能。

  • 连接的创建与管理 :MyBatis 使用PooledDataSource类来创建和管理数据库连接。该类实现了DataSource接口,并使用标准的 JDBC API 来获取连接。它会维护一个连接的池,每当请求新的连接时,首先会检查连接池中是否有可用的连接,如果有,则直接返回;如果没有,则创建新的连接。
  • 连接的复用:通过连接池,MyBatis可以重用已经创建的连接。当请求完成后,该连接不会被关闭,而是返回到连接池中,以便后续请求再次使用。这种方式显著减少了创建连接的开销。
  • 连接的关闭与回收:在应用程序的生命周期中,连接池还需要定期检查并关闭超时未使用的连接,以维护资源的有效性。在 MyBatis中,可通过设置最大连接数、最大空闲时间等参数来控制。
  1. 核心源码分析 {#2-核心源码分析} =====================

Mybatis的源码类整体结构如下图(本文基于 MyBatis 3.5.7):
img

下面,我们具体分析几个核心的类:

2.1 PooledDataSource {#2-1-PooledDataSource}

在 MyBatis 中,PooledDataSource 是其内置连接池的实现,负责管理可重用数据库连接。其核心概念是通过维护一组连接的池,减少频繁创建和销毁连接所带来的性能开销。

PooledDataSource 类在 MyBatis 的 org.apache.ibatis.datasource 包中实现,与多个其他类一起协同工作来管理连接。其结构如下:

  • PooledDataSource:主要的连接池类,管理连接的创建、分配和回收。
  • PooledConnection :对每个 JDBC 连接的包装类,封装了 JDBC Connection 对象,管理连接的状态。

2.1.1 初始化连接池 {#2-1-1-初始化连接池}

img

在构造函数中,PooledDataSource 提供了多种方式来初始化连接池,包括一些基本参数,如数据库 URL、用户名和密码,连接池的大小(默认为10)以及最大闲置时间(默认为30秒),这些参数可以通过 MyBatis 配置文件设置。

2.1.2 获取连接 {#2-1-2-获取连接}

PooledDataSource 类中,定义了两个关键的方法来获取数据库连接:

|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 | @Override public Connection getConnection() throws SQLException { return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return popConnection(username, password).getProxyConnection(); } |

这两个方法内部都会调用popConnection方法,源码如下:

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 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) { if (!state.idleConnections.isEmpty()) { // Pool has available connection conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else { // Pool does not have available connection if (state.activeConnections.size() < poolMaximumActiveConnections) { // Can create new connection conn = new PooledConnection(dataSource.getConnection(), this); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "."); } } else { // Cannot create new connection PooledConnection oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > poolMaximumCheckoutTime) { // Can claim overdue connection state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; state.activeConnections.remove(oldestActiveConnection); if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { try { oldestActiveConnection.getRealConnection().rollback(); } catch (SQLException e) { log.debug("Bad connection. Could not roll back"); } } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); } } else { // Must wait try { if (!countedWait) { state.hadToWaitCount++; countedWait = true; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); } long wt = System.currentTimeMillis(); state.wait(poolTimeToWait); state.accumulatedWaitTime += System.currentTimeMillis() - wt; } catch (InterruptedException e) { break; } } } } if (conn != null) { // ping to server and check the connection is valid or not 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 { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection."); } state.badConnectionCount++; localBadConnectionCount++; conn = null; if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Could not get a good connection to the database."); } throw new SQLException("PooledDataSource: Could not get a good connection to the database."); } } } } } if (conn == null) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } return conn; } |

popConnection方法会检查当前池中是否有可用连接:

  • 如果连接池中有空闲连接,就从 idleConnections 列表中移除并返回该连接。这里使用 remove(0) 方法获取并移除列表的第一个元素,确保获取的是最旧的连接。
  • 如果没有空闲连接,且当前活跃连接数小于最大活跃连接数,可以创建新的连接。
  • 如果已有的活跃连接已达到最大数量,且没有空闲连接,方法会检查最旧的活跃连接的检出时间。
  • 如果超时,则将其声明为过期连接,并尝试获取其持有的资源;否则,当前线程必须等待可用连接。
  • 如果必须等待连接,方法会调用 wait(),使当前线程挂起,同时记录等待时间和次数。
  • 在成功获得连接后,还需检查其有效性。如果有效,则进行一些准备工作,如回滚事务、设置连接属性等。
  • 若连接无效,则增加坏连接计数,并尝试重新获取连接。
  • 如果无法得到有效连接,抛出 SQLException 表示连接池发生了严重错误。
  • 如果一切正常则返回连接

总结:

popConnection方法负责从 MyBatis 的连接池中获取连接,详细设计考虑了多个方面,包括:

  • 连接的复用与生命周期管理:通过维护空闲连接和活跃连接,有效控制连接的生命周期,减少开销。
  • 并发控制:使用 synchronized 确保在多线程环境下的安全性,避免连接状态混乱。
  • 连接的有效性检查:在获取连接后,确保连接仍是有效的,避免因无效连接导致的错误。
  • 超时管理与连接等待:通过定义一定的等待策略,避免因连接争用引发的资源竞争。

2.1.3 关闭连接 {#2-1-3-关闭连接}

关闭连接池的核心源码如下:
img

当需要关闭连接池时,forceCloseAll 方法会被调用,它会遍历连接池中的所有PooledConnection实例并调用它们的 forceClose 方法,确保所有连接被关闭并释放资源。

  1. PooledConnection {#3-PooledConnection}

PooledConnection 类是包装 JDBC Connection 对象的类,提供了额外的功能和方法。其核心代码如下:

|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 | 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; this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); } |

PooledConnection 维护着一个真实的 JDBC 连接实例,以及一个可用状态标志。可以通过 isAvailable 来判断连接是否可以使用。

  1. 连接池的管理逻辑 {#4-连接池的管理逻辑}

PooledDataSource 的实现中,连接的获取和归还之间包含了若干管理逻辑,以确保连接的有效性和可用性:

  • 检查连接可用性:在连接使用之前,会检查连接状态,确保其未被占用。
  • 连接超时管理:将连接的最大闲置时间作为管理参数,如果连接在一定时间内未使用,则通过定时任务回收这些连接。
  • 连接数限制:池中最大连接数的限制可以防止过多的连接被创建,避免因资源浪费而降低性能。
  1. 优缺点 {#5-优缺点} ===============

5.1 优点 {#5-1-优点}

  • 简单易用:MyBatis 自带连接池实现简单,适合快速开发和少量数据库操作的场景。
  • 无外部依赖:作为 MyBatis 的一部分,使用内置连接池不需要额外添加依赖。

5.2 缺点 {#5-2-缺点}

  • 功能单一:与其他成熟的连接池相比,MyBatis 自带连接池的功能较为简单,缺乏一些高级特性,如连接健康检查、连接存活时间管理等。
  • 性能限制:在高并发环境下,MyBatis 内置连接池可能遭遇性能瓶颈,易出现连接争用或等待。
  1. 总结 {#6-总结} =============

这篇文章,我们详细地分析了MyBatis内置线程池的原理以及核心源码分析,内置的连接池适合于简单的应用场景,随着项目复杂度的增加,特别是在高并发的情况下,使用如 HikariCP、C3P0 等更成熟的连接池实现。

  1. 学习交流 {#7-学习交流} =================
赞(0)
未经允许不得转载:工具盒子 » MyBatis 内置连接池原理详解!