Mybatis源码学习:PooledDataSource

本文详细解读了Mybatis中PooledDataSource、UnpooledDataSource、PooledConnection和PoolState的关键实现,包括动态代理的运用、连接池状态管理与数据库连接获取策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述:

本文将会学习PooledDataSource、UnpooledDataSource、PooledConnection和PoolState这四个类的源码实现,mybatis通过这四个类实现了一个简单的数据库连接池,这四个类的功能分别为:

  • PooledDataSource:数据库的连接池
  • UnpooledDataSource: 非池的数据库链接 (创建真正的链接)
  • PooledConnection:连接池中的连接对象
  • PoolState:提供一些统计信息,用于监控当前连接池的状态

下面来分别学习这四个个类的代码实现

PooledConnection:

PooledConnection类实现了InvocationHandler接口,也就是说这是一个动态代理类,主要是代理了Connection#close方法

属性:
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;
//连接的代理对象,使用jdk的基于接口的动态代理
private final Connection proxyConnection;
//等待时间戳
private long checkoutTimestamp;
//连接创建时的时间戳
private long createdTimestamp;
//上一次使用时的时间戳
private long lastUsedTimestamp;
//相当于 hash(url + user + password)
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;
  // 生成代理类
  this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}

invoke

重点分析invoke方法,这里代理了Connection对象的close方法,是实现连接池功能的关键

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //获取方法名
  String methodName = method.getName();
  //如果当前调用的是close方法,即需要关闭当前连接,则将当前连接对象重新push到连接池中,实现连接的复用
  if (CLOSE.equals(methodName)) {
    dataSource.pushConnection(this);
    return null;
  }
  try {
    //当调用继承自Obejct类的方法时,抛出SQLException,而不是一个运行时异常
    //对应issue #579
    if (!Object.class.equals(method.getDeclaringClass())) {
      checkConnection();
    }
    //调用实际方法
    return method.invoke(realConnection, args);
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

PoolState

连接池状态统计对象,逻辑简单,直接看代码

public class PoolState {
  //数据源对象
  protected PooledDataSource dataSource;
  //空闲连接集合
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  //活动连接集合
  protected final List<PooledConnection> 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;

  public PoolState(PooledDataSource dataSource) {
    this.dataSource = dataSource;
  }

  public synchronized long getRequestCount() {
    return requestCount;
  }
  //平均请求时间
  public synchronized long getAverageRequestTime() {
    return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
  }
  //平均等待时间
  public synchronized long getAverageWaitTime() {
    return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;
  }

  public synchronized long getHadToWaitCount() {
    return hadToWaitCount;
  }

  public synchronized long getBadConnectionCount() {
    return badConnectionCount;
  }

  public synchronized long getClaimedOverdueConnectionCount() {
    return claimedOverdueConnectionCount;
  }
  //平均超时等待时间
  public synchronized long getAverageOverdueCheckoutTime() {
    return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
  }
  //平均等待时间
  public synchronized long getAverageCheckoutTime() {
    return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
  }

  public synchronized int getIdleConnectionCount() {
    return idleConnections.size();
  }

  public synchronized int getActiveConnectionCount() {
    return activeConnections.size();
  }

  @Override
  public synchronized String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("\n===CONFINGURATION==============================================");
    builder.append("\n jdbcDriver                     ").append(dataSource.getDriver());
    builder.append("\n jdbcUrl                        ").append(dataSource.getUrl());
    builder.append("\n jdbcUsername                   ").append(dataSource.getUsername());
    builder.append("\n jdbcPassword                   ").append(dataSource.getPassword() == null ? "NULL" : "************");
    builder.append("\n poolMaxActiveConnections       ").append(dataSource.poolMaximumActiveConnections);
    builder.append("\n poolMaxIdleConnections         ").append(dataSource.poolMaximumIdleConnections);
    builder.append("\n poolMaxCheckoutTime            ").append(dataSource.poolMaximumCheckoutTime);
    builder.append("\n poolTimeToWait                 ").append(dataSource.poolTimeToWait);
    builder.append("\n poolPingEnabled                ").append(dataSource.poolPingEnabled);
    builder.append("\n poolPingQuery                  ").append(dataSource.poolPingQuery);
    builder.append("\n poolPingConnectionsNotUsedFor  ").append(dataSource.poolPingConnectionsNotUsedFor);
    builder.append("\n ---STATUS-----------------------------------------------------");
    builder.append("\n activeConnections              ").append(getActiveConnectionCount());
    builder.append("\n idleConnections                ").append(getIdleConnectionCount());
    builder.append("\n requestCount                   ").append(getRequestCount());
    builder.append("\n averageRequestTime             ").append(getAverageRequestTime());
    builder.append("\n averageCheckoutTime            ").append(getAverageCheckoutTime());
    builder.append("\n claimedOverdue                 ").append(getClaimedOverdueConnectionCount());
    builder.append("\n averageOverdueCheckoutTime     ").append(getAverageOverdueCheckoutTime());
    builder.append("\n hadToWait                      ").append(getHadToWaitCount());
    builder.append("\n averageWaitTime                ").append(getAverageWaitTime());
    builder.append("\n badConnectionCount             ").append(getBadConnectionCount());
    builder.append("\n===============================================================");
    return builder.toString();
  }

}

UnpooledDatasource

创建真正的数据库连接

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class UnpooledDataSource implements DataSource {
  
  private ClassLoader driverClassLoader;
  private Properties driverProperties;
  private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();
 //常规配置
  private String driver;
  private String url;
  private String username;
  private String password;
 //是否自动commit    事务的隔离级别
  private Boolean autoCommit;
  private Integer defaultTransactionIsolationLevel;

 //注册驱动
  static {
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
  }

  public UnpooledDataSource() {
  }

  public UnpooledDataSource(String driver, String url, String username, String password) {
    this.driver = driver;
    this.url = url;
    this.username = username;
    this.password = password;
  }

 //重点getconnnection  获取数据库连接,调用doGetConnection方法
  @Override
  public Connection getConnection() throws SQLException {
    return doGetConnection(username, password);
  }

  @Override
  public Connection getConnection(String username, String password) throws SQLException {
    return doGetConnection(username, password);
  }

 //用户名  密码   设置配置
  private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (driverProperties != null) {
      props.putAll(driverProperties);
    }
    if (username != null) {
      props.setProperty("user", username);
    }
    if (password != null) {
      props.setProperty("password", password);
    }
    return doGetConnection(props);
  }
 //真正获取连接的方法,由此可见,每次调用getConnection方法是,都会新建一个Connection对象,可想当并发量高是,数据库的连接就会被占满
  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }
 //初始化  在调用doGetConnection方法中,会先进行一下判断,判断mybatis的configuration中是否已经有数据库驱动,没有的话会通过反射加载驱动
  private synchronized void initializeDriver() throws SQLException {
    if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        Driver driverInstance = (Driver)driverType.newInstance();
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }
 //设置是否自动提交   事务的隔离级别
  private void configureConnection(Connection conn) throws SQLException {
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }


}

PooledDataSource

This is a simple, synchronous, thread-safe database connection pool.

这是一个简单,同步,线程安全的数据库连接池
属性
//连接池状态对象,用于统计当前连接池的运行状态
private final PoolState state = new PoolState(this);

private final UnpooledDataSource dataSource;

//可配置的连接池属性
//连接池最大活动连接数
protected int poolMaximumActiveConnections = 10;
//连接池最大空闲连接数
protected int poolMaximumIdleConnections = 5;
//连接池最大等待时间
protected int poolMaximumCheckoutTime = 20000;
//连接重试的等待时间
protected int poolTimeToWait = 20000;
//连接池能容忍的最大不良连接数
protected int poolMaximumLocalBadConnectionTolerance = 3;
//连接保活sql
protected String poolPingQuery = "NO PING QUERY SET";
//是否允许连接保活
protected boolean poolPingEnabled;
//执行保活sql的时间间隔,单位为毫秒
protected int poolPingConnectionsNotUsedFor;
//需要连接类型代码 hash(url+username+password)
private int expectedConnectionTypeCode;

构造函数

和非池化数据源的构造函数类似,目的都是为了获取那几个参数

public PooledDataSource() {
  dataSource = new UnpooledDataSource();
}

public PooledDataSource(UnpooledDataSource dataSource) {
  this.dataSource = dataSource;
}

public PooledDataSource(String driver, String url, String username, String password) {
  dataSource = new UnpooledDataSource(driver, url, username, password);
  expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}

public PooledDataSource(String driver, String url, Properties driverProperties) {
  dataSource = new UnpooledDataSource(driver, url, driverProperties);
  expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}

public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
  dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
  expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}

public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
  dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
  expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}

获取连接
@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方法,popConnection方法的代码如下:

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 {
        //活跃连接集合的大小小于配置的连接池最大活动连接数
        if (state.activeConnections.size() < poolMaximumActiveConnections) {
          // 还能创建新的对象,直接创建连接对象就完事儿了
          conn = new PooledConnection(dataSource.getConnection(), this);
          if (log.isDebugEnabled()) {
            log.debug("Created connection " + conn.getRealHashCode() + ".");
          }
        } 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) {
                log.debug("Bad connection. Could not roll back");
              }
            }
            //重新创建一个新的PooledConnection对象。
            //注意,这里PooledConnection对象中包装的真实连接没有变
            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 {
            //进入这里表示连接池中没有可以剔除的连接,则当前线程会被阻塞在这里,
            //直到连接池中有新的连接后被唤醒
            try {
              if (!countedWait) {
                state.hadToWaitCount++;
                countedWait = true;
              }
              if (log.isDebugEnabled()) {
                log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
              }
              long wt = System.currentTimeMillis();
              //调用Object.wait方法阻塞当前线程
              state.wait(poolTimeToWait);
              state.accumulatedWaitTime += System.currentTimeMillis() - wt;
            } catch (InterruptedException e) {
              break;
            }
          }
        }
      }
      if (conn != null) {
        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置为空,继续自旋
          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方法执行的流程图如下所示:
在这里插入图片描述

获取连接方法的思考

1、方法中剔除最老的连接的方案只是一种思路,觉得在这里定义一个剔除策略的接口,并内置几个默认策略。

这样做的好处一是可以提供多种策略让用户选择,二是也算是在这里留了一个扩展点,方便用户自己扩展

2、方法中阻塞线程使用的是Object.wait()/Object.notify()的api,觉得在这里还是使用JUC下的ReentrantLock的api可能会好一点

归还连接

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();
        }
        //这里为什么要重新创建一个新的连接对象?查了资料,别人的解释是避免使用方还在使用conn,
        // 通过将它设置为失效,万一使用方再次调用,会抛出异常
        PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
        state.idleConnections.add(newConn);
        newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
        newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
        conn.invalidate();
        if (log.isDebugEnabled()) {
          log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
        }
        //唤醒阻塞在state对象上的线程
        state.notifyAll();
      } else {
        //走到这里表示空闲连接集合已经满了,所以当前连接无家可归了,直接就可以被关闭了
        state.accumulatedCheckoutTime += conn.getCheckoutTime();
        if (!conn.getRealConnection().getAutoCommit()) {
          conn.getRealConnection().rollback();
        }
        //获取PooledConnection包装的真实连接并将其关闭
        conn.getRealConnection().close();
        if (log.isDebugEnabled()) {
          log.debug("Closed connection " + conn.getRealHashCode() + ".");
        }
        conn.invalidate();
      }
    } else {
      if (log.isDebugEnabled()) {
        log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
      }
      state.badConnectionCount++;
    }
  }
}

pushConnection方法的流程图如下
在这里插入图片描述

连接保活

定期执行poolPingQuery中配置的sql,用于保证数据库连接不会失效

protected boolean pingConnection(PooledConnection conn) {
  boolean result = true;
  //判断真实连接是否已经被关闭
  try {
    result = !conn.getRealConnection().isClosed();
  } catch (SQLException e) {
    if (log.isDebugEnabled()) {
      log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
    }
    result = false;
  }
  //允许连接保活 --->true
  //保活sql执行的时间间隔  ---> 大于0
  //当时时间-连接上次执行时间 --->大于保活sql执行的时间间隔
  if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
      && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
    try {
      if (log.isDebugEnabled()) {
        log.debug("Testing connection " + conn.getRealHashCode() + " ...");
      }
      //获取真实连接
      Connection realConn = conn.getRealConnection();
      //执行poolPingQuery中配置的sql语句
      try (Statement statement = realConn.createStatement()) {
        statement.executeQuery(poolPingQuery).close();
      }
      //不是自动提交事务的话就回滚
      if (!realConn.getAutoCommit()) {
        realConn.rollback();
      }
      //一切ok,就返回true,表示当前连接没问题
      result = true;
      if (log.isDebugEnabled()) {
        log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
      }
    } catch (Exception e) {
      log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
      try {
        conn.getRealConnection().close();
      } catch (Exception e2) {
        // ignore
      }
      result = false;
      if (log.isDebugEnabled()) {
        log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
      }
    }
  }
  return result;
}

关闭所有连接

代码逻辑为遍历活跃连接集合和空闲连接集合,将集合中的每一个连接对象置为无效,并将该对象包装的真实连接关闭。

public void forceCloseAll() {
  synchronized (state) {
    expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
    for (int i = state.activeConnections.size(); i > 0; i--) {
      try {
        PooledConnection conn = state.activeConnections.remove(i - 1);
        conn.invalidate();

        Connection realConn = conn.getRealConnection();
        if (!realConn.getAutoCommit()) {
          realConn.rollback();
        }
        realConn.close();
      } catch (Exception e) {
        // ignore
      }
    }
    for (int i = state.idleConnections.size(); i > 0; i--) {
      try {
        PooledConnection conn = state.idleConnections.remove(i - 1);
        conn.invalidate();

        Connection realConn = conn.getRealConnection();
        if (!realConn.getAutoCommit()) {
          realConn.rollback();
        }
        realConn.close();
      } catch (Exception e) {
        // ignore
      }
    }
  }
  if (log.isDebugEnabled()) {
    log.debug("PooledDataSource forcefully closed/removed all connections.");
  }
}

END!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值