Mybatis的数据库连接池

在Mybatis的配置中,如果采用连接池的方式管理数据库连接池,那么就会开启数据库连接池。

采用连接池的数据源PooledDataSource实则是非连接池数据源UnpooledDtaSource的包装类。

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

 

其中,数据库连接池的状态由PoolState来保管,其中最重要的是其存在两个存放数据库连接的数组。

 

protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();

 

顾名思义,一个则是空闲的数据库连接,另一个则是正在使用中的数据库连接,数据库连接池所管理的连接,就是保存在这两个数组当中。

 

 

通过getConnection()方法获取数据库连接,但在其get方法中,首先还是会通过popConnection()方法从连接池中获取连接。

@Override
public Connection getConnection() throws SQLException {
  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) {
      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) {
                /*
                   Just log a message for debug and continue to execute the following
                   statement like nothing happend.
                   Wrap the bad connection with a new PooledConnection, this will help
                   to not intterupt current executing thread and give current thread a
                   chance to join the next competion for another valid/good database
                   connection. At the end of this loop, bad {@link @conn} will be set as null.
                 */
                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;
}

这个方法中,可以清楚看到整个数据库连接池的逻辑了。

 

首先,判断存放空闲数据库连接的数组是否为空,如果不为空 ,则说明此时已经存在闲置的数据库连接,那么可以直接返回该数组的第一个连接。

但是,如果此时的数据库连接池并没有空闲的连接,那么就需要重新建立一个新的数据库连接。但这样做的前提是此时数据库连接池中的连接数量小于规定的最大连接数量。

所以,一旦,因为此时的连接数量已经不小于当前规定的最大连接数量,那么就会从存放正在运行的数据库连接的数组中取第一个连接,根据当前时间计算得到已经运行时间,与固定的最长时间进行比较,如果超过,则将其移出,回滚其事务,建立一个新的数据库连接(但数据库的链接还是原来那个被判断超时的那个数据库连接),放入活跃的数据库连接池。

 

数据库连接池中的连接由PooledConnection来表示。

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);
}

 

可以看到,在其构造方法中除了一些常规的参数设置之外,还通过jdk生成了一个代理类来保存。

 

回到最一开始的getConnection(),也可以清楚地看到,在通过popConnection()得到PooledConnection之后,最后得到的还是其代理连接。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  String methodName = method.getName();
  if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
    dataSource.pushConnection(this);
    return null;
  } else {
    try {
      if (!Object.class.equals(method.getDeclaringClass())) {
        // issue #579 toString() should never fail
        // throw an SQLException instead of a Runtime
        checkConnection();
      }
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
}

以上就是PooledConnection中实现的invoke()方法,这里会检查代理类所要调用的方法名。如果对应的方法是close方法,那么直接会调用连接池的pushConnection()方法。其他的方法,则会直接调用数据库连接来完成相应的操作。

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 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.notifyAll();
      } else {
        state.accumulatedCheckoutTime += conn.getCheckoutTime();
        if (!conn.getRealConnection().getAutoCommit()) {
          conn.getRealConnection().rollback();
        }
        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++;
    }
  }
}

在关闭连接的时候,会先从活跃的数据库连接数组当中移出,之后根据规定的空闲数据库连接大小放入空闲数据库连接大小,这里重新生成了一个PooledConnection,但是其中数据库的连接还是原来的,并没有数据库连接发生变化。

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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、付费专栏及课程。

余额充值