mybatis自带连接池使用

2022-09-27 13:17:18

使用mybatis自带的连接池获得数据库连接,对数据库进行操作.

获得连接和关闭连接都是用mybatis自带的连接池,节省资源.

获取连接

获得连接.在使用mapper进行数据库操作时,会使用JdbcTransaction获得连接.

JdbcTransaction

 protected DataSource dataSource;

Connection connection = dataSource.getConnection();

获取连接.PooledDataSource.popConnection().
 while (conn == null) {}  当获得的连接不为空时返回,否则一直执行.

1.当空闲连接不为空时,从空闲连接中获取连接,并将连接从空闲连接中去除.

if (state.idleConnections.size() > 0) {
          // Pool has available connection
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }

2.当空闲连接为空时,判断正在使用的连接的数量是否小于设置的连接池最大使用连接数,如果小于,则新建连接

else {
          // Pool does not have available connection
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection
            conn = new PooledConnection(dataSource.getConnection(), this);
            @SuppressWarnings("unused")
            //used in logging, if enabled
            Connection realConn = conn.getRealConnection();
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }

3.当空闲连接为空,正在使用连接等于连接池最大连接时,不能创建新的连接,只能等待旧的连接释放

获得最先使用的连接,判断被检出的时间,即使用的时间是否超过设置的最大被检出时间.

如果大于.

声明逾期连接数量 +1

逾期连接累计被检出时间+ 此连接被检出时间

累计被检出时间 + 此连接被检出时间

从使用的连接中移除此连接

如果连接不是自动提交,则回滚

以旧的连接的数据创建新的连接

将旧连接状态置为false.

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()) {
                oldestActiveConnection.getRealConnection().rollback();
              }
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }

4.当空闲连接为空,使用连接等于最大连接,最先使用的连接没有过期,则只能等待连接过期

等待设置的等待时间

累计等待时间 += 等待了的时间

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

5.当获得连接,且连接部位null,则跳出了while循环

5.1.判断连接是否可用

如果可用,对连接进行参数设置

设置连接代码,是由url+username+password,进行hashcode获得的int类型的值.

设置被检出时间,和最后的使用时间,都是当前时间.

添加到使用的连接中

请求连接的计数器+1

累计获得请求耗费的时间 += 此方法执行开始到此行代码执行时耗费的时间.

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

5.2.如果连接不可用.

坏连接数量+1

本地坏连接数量+1 

连接位置null,会继续获取连接,

else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;

5.2.2.如果连续获取连接都是坏连接.且坏连接的数量>设置的空闲连接的最大值+3

则报错,扔出异常
            if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
              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.");
            }

6.获得连接后,对连接进行非空判断,

如果为空,则抛出异常

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

7.返回获得的连接,pooledConnection.

    return conn;

从上述方法可以看出,

  1. 获取连接时,会从连接池进行获取,
  2. 如果连接池中没有连接,会判断使用的连接数量是否大于设置的总数
  3. 如果小于总数,则创建新的连接
  4. 如果不小于总数,则需要复用连接,
  5. 判断最先使用的连接是否过期,如果过期,则直接以过期的连接创建新的连接进行使用,过期的连接置为invalid
  6. 如果没有过期的连接,则需要等待设置的等待时间,然后在进行获取连接
  7. 如果连接为null,则会一直进行循环获取.
  8. 获取连接后,对连接进行校验,会调用pingConnection()方法
  9. 如果连接不可用则则为坏连接,并将连接置为null,继续进行获取
  10. 当坏连接的数量>最大空闲连接数量+3时,抛出异常,
  11. 最后对连接进行!null判断,如果获得的连接为空,则抛出异常.

关闭连接

  1. 单独的mybatis框架使用的是SqlSession的默认实现类,DefaultSqlSession,
  2. 这个类不是线程安全的,他有成员属性,而且有方法可以对这个属性进行修改,有的方法需要使用这个属性,就造成,如果是多个线程同时使用,会不安全.
  3. 所以,我们单独使用mybatis时,是直接从Factory中new一个SqlSession,然后使用完成后,要关闭.每次都是用新的来操作.
  4. 当使用spring和mybatis整合的时候,可以使用SqlSessionTemplate来实现对session的代理.
  5. 当SqlSessionTemplate交给spring管理的时候,会在全局创建一个session,单例的,所有的dao公用同一个session.
  6. 因为SqlSessionTemplate是线程安全的,
  7. 且,SqlSessionTemplate在代理方法执行完成后,会有一个session.close().的操作.

具体的源码及执行流程,以后会梳理.

当使用完连接后,要进行关闭,一直搞不明白,为什么需要关闭,对于这些不是很理解.

而且,关闭的话,是关闭session还是关闭connection,对于两者的关系,还在梳理中.

使用spring整合的时候,不需要手动关闭session,可能是底层有类会对session进行关闭.

梳理完mybatis后,要梳理spring的源码,把和mybatis的整合看一下.

当关闭连接时,会使用代理,判断调用的方法,如果是ConnectionClosed(),就会调用pushConnection()方法,将连接放回连接池中,并不是关闭连接.如果是别的方法,则还是会正常的执行.

class PooledConnection implements InvocationHandler

 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 (method.getDeclaringClass() != Object.class) {
          // issue #578. 
          // 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实现了InvocationHandler接口,方法直接使用此类进行代理.

当调用invoke方法后,会根据方法名进行判断,如果是close()方法,则将连接放回连接池,不会直接关闭连接.

PooledDataSource.pushConnection

protected void pushConnection(PooledConnection conn) throws SQLException {

1.将连接从使用连接移除

state.activeConnections.remove(conn);

2.判断连接是否可用

if (conn.isValid()) {

2.1.当空闲连接的数量小于设置的最大的空闲连接的数量,且,连接的连接类型代码和记录的连接类型代码相同时

//conn.getConnectionTypeCode(),是每个pooledConnection的属性,使用url+username+password,进行hashcode算法,获得的int类型数据,每个conn的typecode应该是唯一的,typecode属性实在popConnection时设置的.

//expectedConnectionTypeCode是pooledConnection的属性,在创建pooledConnection时赋值

//同一个DataSource的typecode应该是一致的,通过同一个DataSource获得的connection的typecode也是相同的

//typecode = ("" + url + username + password).hashCode()

//同一个连接设置获得的typecode是相同的
 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();
          }

//此处是真正的关闭连接,使用的是connection,不是pooledConnection.
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          conn.invalidate();
        }
      }

3.当连接不可用时,直接丢弃连接,不把连接放入到连接池中

 else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++;
      }

总结,pushConnection

  1. 用户获得连接时获得的是poolConnection中的proxyConnection,即为代理连接.
  2. return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  3. ProxyConnection在pooledConnection中创建,是pooledConnection的属性,在pooledConnection创建时即创建代理.
  4. 且pooledConnection中还有一个属性是Connection,
  5. 当调用close()方法时,会使用代理,判断方法,如果是close(),则将连接放回连接池,如果是别的方法,则执行connection的方法
  6. return method.invoke(realConnection, args);

测试连接

在pop和push时都会对连接的可用性进行判断,会调用pingConnection方法.

PooledDataSource.pingConnection();

protected boolean pingConnection(PooledConnection conn) {

//判断是否开启了验证,这个属性是在配置文件中设置,

if (poolPingEnabled) {

//poolPingConnectionsNotUsedFor是配置文件中的属性.

//conn.getTimeElapsedSinceLastUse() = System.currentTimeMillis() - lastUsedTimestamp

//即当前时间 - 连接创建的时间.如果> 我们配置文件中设置的间隔时间,就进行测试,如果不大于,就不测试

if (poolPingConnectionsNotUsedFor >= 0
                        && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {

log.debug("Testing connection " + conn.getRealHashCode() + " ...");

//执行配置文件中的sql语句,进行测试

Connection realConn = conn.getRealConnection();
                        Statement statement = realConn.createStatement();
                        ResultSet rs = statement.executeQuery(poolPingQuery);
                        rs.close();
                        statement.close();

log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");

//如果测试不成功,则报错,并把连接关闭,并返回false

//如果为false则表示连接不可用,在pop中,会将conn=null,会继续获取连接

//在push中,开始时就从active中移除了conn,如果conn不可用,就不把conn放入idle中.

catch (Exception e) {
                        log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());

conn.getRealConnection().close();

    log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
  • 作者:yue_cui_wu
  • 原文链接:https://blog.csdn.net/yue_cui_wu/article/details/82627770
    更新时间:2022-09-27 13:17:18