Mybatis Source Interpretation--Data Source Module (Factory Mode)

Posted by sane993 on Wed, 17 Jun 2020 02:59:26 +0200

The data source module for Mybatis is located inOrg.apache.ibatis.datasource.

Common data source components are implementedJavax.sql.DataSourceInterface.Within Mybatis, third-party data source components are integrated, and data source implementations are provided.In general, data source initialization is more complex and has more parameters, so the factory mode is used here.

 

Factory Mode

Factory Pattern is a creator mode that provides the best way to create objects.Defines an interface for creating objects so that its subclasses feel like instantiating which factory class, and factory mode delays it until the subclass.

Factory interface: The factory interface is the core interface of the factory mode, and callers get specific implementation classes by interacting directly with the factory interface.
ConcreteFactory: An implementation class of a factory interface used to instantiate product objects. Different specific factory classes instantiate different product implementation classes based on different requirements.
Product interface: A product interface is used to define the functionality of a product class, which must be implemented for all products produced by a specific factory class.
ConcreteProduct: The implementation class of a product interface in which specific business logic is defined.

 

Data Source Module

DataSourceFactory: The factory interface for the Mybatis data source.Mybatis provides two specific factory classes, PooledDataSourceFactory (a data source factory that uses connection pools), UnpooledDataSourceFactory (a data source factory that does not use connection pools), which instances PooledDataSource and UnpooledDataSource, respectively.

public interface DataSourceFactory {

  //Set DataSource related properties
  void setProperties(Properties props);

  //Get Data Source
  DataSource getDataSource();

}
public class UnpooledDataSourceFactory implements DataSourceFactory {

  protected DataSource dataSource;

  public UnpooledDataSourceFactory() {
    this.dataSource = new UnpooledDataSource();
  }
  
  ...

}
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

  public PooledDataSourceFactory() {
    this.dataSource = new PooledDataSource();
  }

}
 

UnpooledDataSource: A data source without a connection pool, where connections are acquired the same way that jdbc manually acquires them.

  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    //Set transaction autocommit, transaction isolation level
    configureConnection(connection);
    return connection;
  }
 

PooledDataSource: A data source with a connection pool.PooledDataSource manages the components of the PooledConnection object state through PoolState, and the connection resources of the idle state and the active state through two list s, respectively.

public class PoolState {
  protected PooledDataSource dataSource;
  //Idle Connection Pool Resource Collection
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  //Active Connection Pool Resource Collection
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  //Number of requests
  protected long requestCount = 0;
  //Cumulative time to acquire connection
  protected long accumulatedRequestTime = 0;
  //Cumulative time spent using connections.Calculate the time of one use from the connection to the return;
  protected long accumulatedCheckoutTime = 0;
  //Number of Timeouts Using Connections
  protected long claimedOverdueConnectionCount = 0;
  //Cumulative timeout
  protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  //Cumulative wait time
  protected long accumulatedWaitTime = 0;
  //Number of Waits 
  protected long hadToWaitCount = 0;
  //Number of invalid connections 
  protected long badConnectionCount = 0;
  
  ...

}

PooledDataSource: A data source with a connection pool.Connection pools are managed through PoolStatus within PooledDataSource, thread pools are taken and put through popConnection and pushConnection, and getConnection calls popConnection.

  protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {//Recycle connections must be synchronous
      state.activeConnections.remove(conn);//Delete this connection from the active connection pool
      if (conn.isValid()) {
    	  //Determine if idle connection pool resources have reached the maximum
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
        	//Upper limit not reached, recycling
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();//Rollback if there are still transactions uncommitted
          }
          //Based on the connection, create a new connection resource and refresh the connection status
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          //Old Connection Failure
          conn.invalidate();
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          //Wake up other blocked threads
          state.notifyAll();
        } else {//If the idle connection pool has reached its maximum limit, the connection is actually closed
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          //Close Real Database Connections
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          //Setting connection object to invalid
          conn.invalidate();
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++;
      }
    }
  }

  private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();//Record attempts to get connection start timestamp
    int localBadConnectionCount = 0;//Number of Initialization Gets Invalid Connections

    while (conn == null) {
      synchronized (state) {//Getting connections must be synchronous
        if (!state.idleConnections.isEmpty()) {//Check for idle connections
          // Pool has available connection
          //Direct use with free connections
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {// No idle connection
          if (state.activeConnections.size() < poolMaximumActiveConnections) {//Determine if the number of active connections in the pool is greater than the maximum number of connections
            // No, you can create a new connection
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {// New connections cannot be created if they are already equal to the maximum number of connections
            //Get the earliest connection created
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {//Detect if maximum usage time has been reached and exceeded
              // Statistics of timeout connection information if timeout occurs
              state.claimedOverdueConnectionCount++;//Timeout Connections+1
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;//Cumulative Timeout Increase
              state.accumulatedCheckoutTime += longestCheckoutTime;//Cumulative increase in connection usage time
              state.activeConnections.remove(oldestActiveConnection);//Remove from active queue
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {//Manual rollback if timeout connection is not committed
                try {
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {//Exceptions only log
                  /*
                     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");
                }  
              }
              //Create a new connection in the connection pool, note that no new connection has been created for the database;
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              //Invalidate old connections
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // There are no idle connections, the earliest connections are not broken, no new connections can be created, only blocked
              try {
                if (!countedWait) {
                  state.hadToWaitCount++;//Connection pool cumulative wait times plus 1
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                state.wait(poolTimeToWait);//Blocking wait specified time
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;//Cumulative wait time increase
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        if (conn != null) {//To get a successful connection, test if the connection is valid and update statistics
          // ping to server and check the connection is valid or not
          if (conn.isValid()) {//Check if the connection is valid
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();//If a legacy transaction, roll back
            }
            //Connection pool related statistics update
            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 the connection is invalid
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;//Cumulative Get Invalid Connections + 1
            localBadConnectionCount++;//Current Number of Get Invalid Connections+1
            conn = null;
            //Get an invalid connection, but if the number of retries is not exceeded, allow another attempt to get the connection or throw an exception
            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;
  }

 

 

Topics: Database Mybatis Apache SQL