Mybatis之連接池


一,前言

​ 連接池有很多種,最為熟悉的比如c3p0,DBCP,druid等。

​ mybatis支持三種內置的數據源類型:

  • Pooled:實現dataSource接口,並且使用了池的思想。
  • UNPooled:同樣也是實現了dataSource接口,但是該類型並沒有使用池的思想。
  • JDNI:采用服務器提供的JDNI技術實現的,並且在不同服務器之間獲取的連接池是不一樣的。

注意:如果項目不是web或者maven的war工程,則是無法使用的。比如Tomcat服務器采用的就是DBCP連接池。

​ 那么在Mybatis種如何配置數據源,如下所示:

<!-- 數據庫連接池 -->
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>

​ 在dataSource屬性中,type標簽可以指定所使用的數據源類型。

二,UnPooled

​ 1,首先從獲取連接的源碼開始。

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

​ 2,接着再進入到doGetConnection()方法中:

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

​ 3,此時又返回一個doGetConnection(),這是重載的另一個方法。

private Connection doGetConnection(Properties properties) throws SQLException {
    // 初始化
    initializeDriver();
    // 獲取一個連接對象
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }

​ 4,第一行代碼中,調用了initializeDriver()方法。

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

​ 大致流程:

  • 在獲取連接對象時,調用initializeDriver()方法判斷是否已經注冊連接驅動。
  • 完成驅動注冊,使用DriverManager.getConnection獲取一個連接對象。
  • 將連接對象交給configureConnection()方法,並設置自動提交事務,及事務的隔離級別。
private void configureConnection(Connection conn) throws SQLException {
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }

這種方式是不具備連接池的思想,如果頻繁的創建和銷毀連接對象,會影響程序的運行效率。

三,Pooled

​ 再來看看使用連接池思想的數據源實現。

​ 在此之前先來說說什么是連接池,連接池就是用於存儲連接對象的一個容器。而容器就是一個集合,且必須是線程安全的,即兩個線程不能拿到同一個連接對象。同時還要具備隊列的特性:先進先出原則。

使用連接池的好處:避免頻繁創建和關閉數據庫連接造成的開銷,節省系統資源。

​ 1,先從獲取連接源碼開始。

@Override
  public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }

​ 2,調用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 {
          // 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;
  }

​ 分析:

​ 1,判斷連接池中是否有空閑的連接對象,有則直接返回。

​ 2,如果連接池沒有空閑的連接,先判斷活動連接池是否小於連接池承載的最大數量,小於則再創建新的連接對象。

​ 3,但是如果連接池已經達到最大承載數量,那么在連接池中就把最先進來的連接(oldest)返回出去。

四,總結

​ 關於JDNI的使用就不分享了,因為博客不太清楚,沒有去研究里面的細節實現,不過后期會對這一點進行補充。

​ 那么關於Mybatis中另外兩種數據源的使用,也總結完了,其最主要的就是關於池的思想,以及使用連接池帶來的好處。

​ 最后以上內容均是自主學習總結,如有不適之處歡迎留言指正。

感謝閱讀!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM