在jdbc中,我们可以通过DriverManager的getConnection方法来获取数据库连接,但是这种方式会去查找相应的驱动,然后与数据库地址建立真实的连接,因此比较耗时。如果在应用中存在大量的连接数据库操作,那么这种方式的效率将会非常低下。为了解决这个问题,数据源的技术应运而生,所谓的数据源,其实就是把获取的连接(Connection)放在一个“池”中,以达到复用的目的。

Mybatis自带的数据源类型有三种:unpooled、pooled以及jndi。其中unpooled会直接从DriverManager获取新连接,而pooled会对连接进行缓存,也就是具有“池”的功能。

首先来看java本身的DataSource接口:

java mybatis 根据租户选择数据源 数据源 mybatis获取数据源_System

 

 

它只有获取Connection的功能。

下面我们首先来看unpooled类型的数据源,它的实现类是:

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;

我们来看看它的获取Connection的方法:

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

  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }

最终调用的是doGetConnection()方法。在该方法中,直接通过DriverManager.getConnection来获取连接。因此unpooled的特点是简单、效率低,不适合用于大量操作数据库的应用。接下来我们来看看具有连接池功能的pooled数据源:

public class PooledDataSource implements DataSource {

  private static final Log log = LogFactory.getLog(PooledDataSource.class);

  //存放Connection的池
  private final PoolState state = new PoolState(this);

  private final UnpooledDataSource dataSource;

  // OPTIONAL CONFIGURATION FIELDS
  protected int poolMaximumActiveConnections = 10;//最大活动连接数
  protected int poolMaximumIdleConnections = 5;//最大空闲连接数
  protected int poolMaximumCheckoutTime = 20000;//活动连接最大过期时间
  protected int poolTimeToWait = 20000;//等待连接最大时间
  protected String poolPingQuery = "NO PING QUERY SET";
  protected boolean poolPingEnabled;
  protected int poolPingConnectionsNotUsedFor;

  private int expectedConnectionTypeCode; //由url、username、password构成的一个hashcode

  …………

这个PooledDataSource里面有一个PoolState,这个就是存放Connection的地方。我们还发现有一个UnpooledDataSource,真正获取连接的操作还是通过UnpooledDataSource来完成。下面我们来看看这个PoolState:

public class PoolState {

  protected PooledDataSource dataSource;
  
  protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();//空闲连接池
  protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();//活动连接池
  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;//无效连接数量
  …………

这里的idleConnections中存放的就是空间的Connection,也就是可以立即拿来用的Connection。而activeConnections里面存放的是活动Connection,也就是正在被使用的连接。

这里我们发现这两个池里面存放的是PooledConnection,而不是Connection,下面我们就来看看这个PooledConnection。

class PooledConnection implements InvocationHandler {

  private static final String CLOSE = "close";
  private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };

  private int hashCode = 0;
  private PooledDataSource dataSource; //所属的DataSource
  private Connection realConnection;//真实Connection
  private Connection proxyConnection;//代理Connection
  ……

  public PooledConnection(Connection connection, PooledDataSource dataSource) {
    ……
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  }

原来这个PooledConnection是一个代理类,我们来看看它的invoke函数:

@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的close方法时,并不会真正关闭这个连接,而是调用了PooledDataSource的pushConnection()方法。我们先不来看这个pushConnection()方法,我们先来看看如何从PooledDataSource获取一个连接:

//获取连接
  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) { //由于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) {
          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 + 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.");
            }
          }
        }
      }

    }

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

当我们想从PooledDataSource获取一个连接时,最终会调用popConnection()这个方法。由于这里涉及对共享变量的操作,这里会对连接池加锁。首先它会查看空闲连接池中是否有连接,如果有则直接使用;如果空闲连接池没有可用连接,那么它会判断当前活动连接是否已达活动连接数的最大值,如果没有达到,那么会调用UnpooledDataSource重新获取一个连接;如果活动连接已达到最大值了,它会尝试从活动连接池中获取一个最早放入的连接,并查看它的过期时间,如果它已经过期,则把该连接移除出活动连接池,然后对未提交了内容执行rollback操作,然后将该连接赋给当前线程使用。如果活动连接池中的所有连接均未过期,那么当前线程将会阻塞,以等待有新的连接可用。

现在我们回过头去看pushConnection()方法:

protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      //首先从活动连接池中移除该Connection
      state.activeConnections.remove(conn);
      if (conn.isValid()) {
        //如果空闲连接池中数量未达到最大值,则把该连接放入空闲连接池,然后调用nitifyAll通知其他线程
        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++;
      }
    }
  }

同样,该操作会对连接池进行加锁,然后将该连接从活动池中移除,接下来查看空闲池中的连接数量是否已达到最大值,如果没有,则把该连接放入空闲池,并调用notifyAll通知其他等待该资源的线程;如果空闲池已满,则关闭该连接。

总结:mybatis内置的datasource还是比较简单的,在实际开发中,我们通常会使用第三方的DataSource,这些第三方的数据源往往能提供更加强大的功能。比如Druid。