The general way to execute an SQL against db via Java JDBC is as follows:

// Load the driver, jdbc2 can be omitted
Class.forName(driver);
// Get the DB connection
Connection connection = DriverManager.getConnection(url, user, password);
/ / create the Statement
Statement statement = connection.createStatement();
// Execute the statement
statement.execute(sql1);
Copy the code

Nowadays, with many persistence frameworks, we don’t use JDBC to operate DB directly anymore, but each framework is also the encapsulation of JDBC, to perform a SQL above the steps are still necessary, perhaps the framework does a little operation so that we don’t have to be aware of these processes.

DataSource is a simple interface that contains only two definitions of interfaces for retrieving connections:

package javax.sql;
public interface DataSource  extends CommonDataSource.Wrapper {
// Get a connection to db
Connection getConnection(a) throws SQLException;

// Get a connection to db. You can specify the user and password to get the connection
Connection getConnection(String username, String password) throws SQLException;
Copy the code

Mybatis provides UNPOOLED and POOLED DataSource. DataSource related code in the SRC/main/Java/org/apache/ibatis/DataSource.

A, UnpooledDataSource

In the configuration file, if the type of the dataSource is not UNPOOLED, the dataSource is UnpooledDataSource. Each time a connection is obtained from the dataSource, a new connection is created and the db connection is closed.

The UnpooledDataSource encapsulates the properties of the DB connection that are retrieved from the configuration file when the configuration is resolved and the connection is retrieved from the values of these properties when the connection is retrieved. Includes the following attributes:

package org.apache.ibatis.datasource.unpooled;

public class UnpooledDataSource implements DataSource {
    / / driver
    private String driver;
    // db connection URL
    private String url;
    / / user name
    private String username;
    / / password
    private String password;
    // Other connection driver attributes, configured through driver.xxx
    private Properties driverProperties;
    
    // Whether the obtained connection is automatically committed
    private Boolean autoCommit;
    // Connect to the default transaction isolation level
    private Integer defaultTransactionIsolationLevel;
    // Connection execution timeout by default
    private Integer defaultNetworkTimeout;
}
Copy the code

These properties can be found in the Mybatis documentation: mybatis.org/mybatis-3/z…

Still get connection by DriverManager. GetConnection method to obtain:

package org.apache.ibatis.datasource.unpooled;

public class UnpooledDataSource implements DataSource {
  // Get the connection
  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }
  
  // Configure the connection properties
  private void configureConnection(Connection conn) throws SQLException {
    if(defaultNetworkTimeout ! =null) {
      conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
    }
    if(autoCommit ! =null&& autoCommit ! = conn.getAutoCommit()) { conn.setAutoCommit(autoCommit); }if(defaultTransactionIsolationLevel ! =null) { conn.setTransactionIsolation(defaultTransactionIsolationLevel); }}}Copy the code

Check whether the driver is loaded by using the initializeDriver method before obtaining the connection. If not, use the specified type loader or class.forname to load the driver. Properties contains the specified user, password, and properties specified through driver.xxx. Get connected through configureConnection method after setting the autoCommit mode, defaultTransactionIsolationLevel and defaultNetworkTimeout connection.

Second, the PooledDataSource

In the configuration file, if the type of the dataSource is not POOLED, the dataSource is PooledDataSource. All connections obtained from the dataSource are POOLED. If the connection is closed, the connection is returned to the free pool without actually closing the connection to the DB.

PooledDataSource implementation in SRC/main/Java/org/apache/ibatis/datasource/pooled PooledDataSource. Java file.

2.1 Creating a DB Connection

PooledDataSource’s connections are pooled, and any of these pools are created by the UnpooledDataSource. PooledDataSource creates an instance of UnpooledDataSource with which new connections are created when needed.

package org.apache.ibatis.datasource.pooled;

public class PooledDataSource implements DataSource {
    // Pool configuration
    // Maximum number of active connections in the pool
    protected int poolMaximumActiveConnections = 10;
    
    private final UnpooledDataSource dataSource;
    public PooledDataSource(a) {
        dataSource = new UnpooledDataSource();
    }
    // The connection-related attributes are set to the dataSource. The other attributes are set the same as the URL
    public void setUrl(String url) { dataSource.setUrl(url); . }}Copy the code

The configuration file driver, url, username, password, defaultTransactionIsolationLevel, defaultNetworkTimeout These attributes to the unconnected pool are also given to the UnpooledDataSource instance.

2.2 a javax.sql.pooledconnection

The connection in PooledDataSource is pooled, which requires that the connection be recycled back to the pool when closed, rather than actually being closed to the DB and releasing the associated resources. So once the connection is retrieved from the UnpooledDataSource, a dynamic proxy PooledConnection is created for the connection.

class PooledConnection implements InvocationHandler {
    // Dynamic proxy only implements the DB Connection class
    private static finalClass<? >[] IFACES =newClass<? >[] { Connection.class };// Dynamic proxy connection
    private final Connection proxyConnection;
    
    private static finalClass<? >[] IFACES =newClass<? >[] { Connection.class };public PooledConnection(Connection connection, PooledDataSource dataSource) {...// Create a dynamic proxy for the connection
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); }}Copy the code

In PooledDataSource’s proxy method, the invoke method, calls to close put the connection back into the free connection pool, while calls to other non-Object methods check whether the connection is available first.

package org.apache.ibatis.datasource.pooled;
class PooledConnection implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        The close method puts the connection back into the pool
        if (CLOSE.equals(methodName)) {
            dataSource.pushConnection(this);
            return null;
        }
        try {
           // Non-object methods verify that the connection is available
           if(! Object.class.equals(method.getDeclaringClass())) { checkConnection(); }// Execute method
           return method.invoke(realConnection, args);
        } catch (Throwable t) {
          throwExceptionUtil.unwrapThrowable(t); }}}Copy the code

In addition to dynamic proxies, PooledConnection includes the following connection information for pooled management:

class PooledConnection implements InvocationHandler {
  // The original connection
  private final Connection realConnection;
  // Dynamic proxy connection
  private final Connection proxyConnection;
  // Check out the time from the pool for use by other threads
  private long checkoutTimestamp;
  // Create time
  private long createdTimestamp;
  // Last usage time
  private long lastUsedTimestamp;
  // 
  private int connectionTypeCode;
  // Whether the connection is valid
  private boolean valid;
}
Copy the code

2.3 PoolState

PoolState records the state of the pool that already holds both idle and active connections (connections by a thread consumer), and each PooledDataSource has an instance of that class. At the same time, the PooledConnection operation is synchronous, using the PoolState instance as the synchronization lock.

package org.apache.ibatis.datasource.pooled;
public class PoolState {
  // Free connection pool
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  // Active connection pool
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
}
Copy the code

Use state as a synchronization lock

public class PooledDataSource implements DataSource {
    private final PoolState state = new PoolState(this);
    
    // Get the connection
    private PooledConnection popConnection(String username, String password) throws SQLException {
        while (conn == null) {
            // Synchronize the operation
            synchronized(state) { ... }}}}Copy the code

2.4 Obtaining connections from the pool

PooledDataSource has free and active connection pools, both of which are initially empty. The basic steps to obtain a connection are as follows:

  1. Returns the first connection from the free pool if there are free connections in the free pool.
PooledConnection conn = null;
while (conn == null) {
  if(! state.idleConnections.isEmpty()) {// From the free pool
    conn = state.idleConnections.remove(0); }}Copy the code
  1. Otherwise, if the active connection pool is not full, create a new connection.
if (state.activeConnections.size() < poolMaximumActiveConnections) {
    conn = new PooledConnection(dataSource.getConnection(), this);
}

Copy the code
  1. Otherwise, try to check out the first connection from the active connection pool.

If the first connection in the active connection pool has been removed from the pool for longer than the poolMaximumCheckoutTime, the connection can be forcibly returned and checked out for use by other threads:

PooledConnection oldestActiveConnection = state.activeConnections.get(0);
// Check out the interval
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
// If the detection time is greater than the maximum, the detection time is detected
if (longestCheckoutTime > poolMaximumCheckoutTime) {
     state.activeConnections.remove(oldestActiveConnection);
}
Copy the code

However, the connections in the active connection pool are those that are still being used by other threads, so the checked out connection may be used by other threads or will be used later, so the checked out connection is rolled back and set to invalid, and then a new PooledConnection is created with its real connection.

// Rollback if auto commit is not set
if(! oldestActiveConnection.getRealConnection().getAutoCommit()) {try {
       oldestActiveConnection.getRealConnection().rollback();
    } catch (SQLException e) {
       log.debug("Bad connection. Could not roll back"); }}// Create a new connection
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
// The old connection is set to invalid
oldestActiveConnection.invalidate();
Copy the code

Of course, the connection may not be currently expired, so wait poolTimeToWait and repeat to step 1.

state.wait(poolTimeToWait);
Copy the code

If an invalid connection is obtained after the previous step, then the number of invalid connections currently obtained is increased by 1. But if the number of invalid connection more than poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance will throw an exception directly:

if (conn.isValid()) {

} else {
    localBadConnectionCount++;
    if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
       throw new SQLException("PooledDataSource: Could not get a good connection to the database."); }}Copy the code

After obtaining a valid connection through the above steps, put the connection into the active connection pool and put it back as follows:

2.4 Closing the Connection

PooledDataSource creates a dynamic proxy PooledConnection for the connection. If the PooledConnection proxy calls the close method, the PooledConnection proxy will be put back into the free pool:

// PooledConnectiond dynamic proxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null; }}Copy the code

The connection may already be invalid when it is put back into the free pool, which can happen in the following cases:

  1. The connection has been uploaded for longer than poolTimeToWait and has been checked out from the active pool by another thread, so the connection is invalid when the original thread connected to the closeer.
  2. Changing the PooledDataSource connection properties such as autoCommit, URL, and password invalidates and closes all connections in the free and active pools.

Invalid connections are ignored instead of being put back into the free pool.

// Put the connection back into the pool
protected void pushConnection(PooledConnection conn) throws SQLException {
    synchronized (state) {
        state.activeConnections.remove(conn);
    }
    if (conn.isValid()) {
        ....
    } else {
       // Invalid connection closing ignoredstate.badConnectionCount++; }}Copy the code

If the connection is valid and the free connection pool is not slow, create a PooledConnection using the real connection of the connection and put it into the free connection pool, then invalidate the original connection.

if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
    // Perform a rollback if the connection is not committed automatically
    if(! conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); }// Add a new connection to the pool
    PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
    state.idleConnections.add(newConn);
    // The original connection is set to invalid
    conn.invalidate();
}
Copy the code

2.5 Verify the connection

A PooledConnection must be checked to see if it is a valid connection. A PooledConnection must be pinged to see if it is a valid connection. To remove the following connection itself, such as the connection has been set invalid by the DB side, such errors can still be found through the ping check.

class PooledConnection implements InvocationHandler {
  public boolean isValid(a) {
    returnvalid && realConnection ! =null && dataSource.pingConnection(this); }}Copy the code

Verify that the real Connection is closed before ping:

protected boolean pingConnection(PooledConnection conn) {
    boolean result = true;
    try{ result = ! conn.getRealConnection().isClosed(); }catch (SQLException e) {
      result = false; }}Copy the code

If the connection is not closed and opened poolPingEnabled configuration, at the same time, the connection of the last time to the present time interval greater than poolPingConnectionsNotUsedFor configuration of time then to launch poolPingQuery db configuration statement execution to test, If the statement does not throw an exception, the connection is available:

if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
        && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
    Connection realConn = conn.getRealConnection();
    try (Statement statement = realConn.createStatement()) {
        statement.executeQuery(poolPingQuery).close();
    }
    result = true;
}
Copy the code

Create DataSorce

To configure DataSorce in the environment, you need to specify the type and various configuration parameters. Details: mybatis.org/mybatis-3/z…

<environment id="development">
   <dataSource type="UNPOOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
</environment>
Copy the code

When creating the SqlSessionFactory using the build method of the SqlSessionFactoryBuilder, Through the SRC/main/Java/org/apache/ibatis/builder/XML/XMLConfigBuilder Java to parse the configuration file, After parsing the Configuration and the SQL statement in the Configuration (org/apache/ibatis/session/Configuration. The Java). The dataSource configuration item is parsed during parsing and the DataSorce is generated based on the configuration.

Get the factory class for the type at parse time via the Type attribute and create an instance. Then set the property to the DataSorce via reflection:

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if(context ! =null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      // Create the factory class instance
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      // Set the properties
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }
Copy the code

PooledDataSourceFactory and UnpooledDataSourceFactory new directly corresponding to the DataSource. The DataSource obtained by the factory method is placed in the Configuration environment for later execution.

The use of DataSorce

4.1 Transaction Provides connections

Each SqlSession has an Executor that executes SQL, and each Executor has a transaction manager that is generated through the transactionManager configuration, The Transaction also holds the dataSource.

// src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSessionFactory.java
public class DefaultSqlSessionFactory implements SqlSessionFactory {
   
  / / generated SqlSession
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // Generate the transaction manager and give the dataSource to the transaction manager
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // Generate an executor for sqlSession
      final Executor executor = configuration.newExecutor(tx, execType);
      return newDefaultSqlSession(configuration, executor, autoCommit); }}Copy the code

SqlSession executes statements that are executed by executor. Executor’s transaction manager is responsible for providing db connections and closing DB connections in addition to providing commit rollback operations. Here is the definition of a Transaction interface:

// src/main/java/org/apache/ibatis/transaction/Transaction.java
public interface Transaction {
    // Get the connection used only by the current executor
    Connection getConnection(a) throws SQLException;
    // Close the connection executor is currently using
    void close(a) throws SQLException;
    
    // Other transaction-related interfaces. }Copy the code

Mybatis provides two classes of JDBC and MANAGED transaction managers. The two types implement the interface in the same way, both of which are in getConnection to determine whether the connection property is null. If it is not null, it gets one from its dataSource and gives it to the Connection property.


// src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java
public class JdbcTransaction implements Transaction {
    protected Connection connection;
    protected DataSource dataSource;
    
    // The connection has a direct return connection
    @Override
    public Connection getConnection(a) throws SQLException {
      if (connection == null) {
        openConnection();
      }
      return connection;
    }
   
    // Get the connection from the dataSource
    protected void openConnection(a) throws SQLException {
        connection = dataSource.getConnection();
     // ...}}Copy the code

4.2 When will the connection be obtained

When a Statement is actually executed against db, the connection is retrieved from the transaction manager to generate the Statement that executes the SQL. In Executor, all statements that are not available from the cache or whose Statement is cached will first be fetched from transaction management to produce the Statement that executes the Statement.

Fetch connections are defined in the BaseExecutor superclass of all executors:

// src/main/java/org/apache/ibatis/executor/BatchExecutor.java
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    returnconnection; }}Copy the code

4.3 When do I Release the Connection

Connection release depends on closing sqlSesson, closing Executor when closing sqlSession, and closing the connection in Eecuto’s close method.

public class DefaultSqlSession implements SqlSession {
    public void close(a) {
        try {
            executor.close(isCommitOrRollbackRequired(false)); . }finally{... }}}public class JdbcTransaction implements Transaction {
  public void close(a) throws SQLException {
    if(connection ! =null) { resetAutoCommit(); connection.close(); }}}Copy the code

Connections are a scarce and important resource and sqlSession is not thread safe. Therefore, sqlSession must be closed to release occupied connections. Mybatis recommends that the sqlSesion lifecycle should be request-in-request.