SoFunction
Updated on 2025-03-08

MyBatis basic support DataSource to implement source code parsing

DataSource

In database applications, the connection object (Connection) established by the client and the database server is a valuable resource. Each time the database is requested, the connection is created, and the connection will be destroyed after use. This is a very resource-consuming operation. Therefore, Java proposes the DataSource interface. Can treat it as aConnection pool. When the program is initialized, a batch of connections is created and placed in the connection pool. If you need to request the database, the connection object (Connection) will be taken out from the connection pool and returned to the connection pool after use. This reduces the steps of creating and destroying connections for every request, thereby improving database performance.

package ;
public interface DataSource  extends CommonDataSource, Wrapper {
  // The most important method  Connection getConnection() throws SQLException;
  // Other methods are no longer listed}

Java only released the interface specification in JDK1.4 version. The specific implementation needs to be implemented by the user. MyBatis provides 3 implementations of DataSource interfaces.

  • UnpooledDataSource
  • PooledDataSource
  • JNDI interface (not within the scope of this article)

The following focuses on analyzing the implementation of two DataSources: 1 and 2.

UnpooledDataSource

As the name suggests, UnpooledDataSource is a non-pooled DataSource. To put it bluntly, it is no different from an ordinary Connection. Connecting through UnpooledDataSource in the past requires recreating a Connection every time. Let's take a look at its getConnection implementation method.

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

In the UnpooledDataSource#getConnection method, the doGetConnection method is called, and the parameters are username and password. This method means to obtain the database connection through the username and password. The specific implementation of doGetConnection uses DriverManager to obtain connection objects. This is how JDBC gets the connection object natively.

It is worth mentioning that the other methods of UnpooledDataSource are implemented based on DriverManager.That is to say, using UnpooledDataSource as a connection pool is equivalent to not using a connection pool.

PooledDataSource

PooledDataSource is the true connection pool, which provides cannibal settings such as the size of the connection pool (default 10), the maximum number of active connections, and the number of idle connections. And the Connection object is dynamically proxyed, and the Connection'sclosemethod. Make the Connection object call the close method to actually close the connection, but instead customize the closing behavior. The closing logic of MyBatis is to return the Connection object to the connection pool.

Let's first look at several important field information of PooledDataSource

public class PooledDataSource implements DataSource {
  // PooledDataSource is the PoolState that really manages the connection state, which will be explained in detail later  private final PoolState state = new PoolState(this);
  // UnpooledDataSource said above is no different from ordinary Connection  private final UnpooledDataSource dataSource;
  //The number of connections being used  protected int poolMaximumActiveConnections = 10;
  //Number of idle connections  protected int poolMaximumIdleConnections = 5;
  //The time when the connection in the pool is checked before being forced to return  protected int poolMaximumCheckoutTime = 20000;
  //This is a low-level setting for giving the connection pool a chance to print the log status, and there is also a retry attempt to get the connection, which often takes a long time in order to avoid silent failure when the connection pool is not configured).  protected int poolTimeToWait = 20000;
  //The detection query sent to the data is used to verify whether the connection is working properly and to prepare to accept the request.  The default is "NO PING QUERY SET", which will cause many database driver connections to fail due to an error message.  protected String poolPingQuery = "NO PING QUERY SET";
  //Enable or disable detection query  protected boolean poolPingEnabled = false;
  //Used to configure poolPingQuery The time is used once multiple times  protected int poolPingConnectionsNotUsedFor = 0;
  private int expectedConnectionTypeCode;
}

These fields mainly record important information about the connection pool: connection pool size, maximum number of connections when idle, maximum number of active connections, timeout time, etc. And to completely unveil the mystery of PooledDataSource to obtain connection objects, two classes need to be introduced. PooledConnection and PoolState

PooledConnection

PooledConnection implements the InvocationHandler interface, which is used as JDK dynamic proxy. As mentioned earlier, mybatis rewrites the close method of the Connection object using JDK dynamic proxy, which is the logic implemented in this class. This class has several important properties.

  • private PooledDataSource dataSource; // copy of dataSource
  • private Connection realConnection; // Real connection object
  • private Connection proxyConnection; // The actual returned proxy object

Next, let’s take a look at how the invoke method of the proxy object rewrites the close method.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  String methodName = ();
  //If you call close, ignore it and add this connection to the pool instead  if (() == () && (methodName)) {
    (this);
    return null;
  } 
  return (realConnection, args);
  // Other logic omitted...}

In the invoke method, determine whether the method name executed is Close. If so, the original close method will no longer be executed, but the pushConnection method of PooledDataSource will be executed! From the method name, we can see that the function of the method is: push the connection into the PooledDataSource of the connection pool. The logic of pushConnection is detailed later.

PoolState

The above mentioned that PooledDataSource does not manage connection objects. So where are the batch of connections created when the program is initialized? The answer is that it exists in the PoolState object, and PooledDataSource has a property called PoolState. In other words, PooledDataSource manages connection pools through PoolState.

A batch of connections is a List collection in Java. So let’s think about how PoolState needs to manage connections?First, according to the status of the connection, you can divide the connection into two types.

  • Idle connectionprotected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
  • Active connectionprotected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();

Two List properties in PoolState store idle connections and active connections respectively. When you need to connect, go fromidleConnectionsTake the list, and the connection is taken fromactiveConnectionsMove toidleConnectionsmiddle.

There are some other statistical information fields in PoolState, such as the number of requests, total time of requests, total number of connections, etc., and they are relatively simple and will not be listed.

Get the connection

After introducing the two classes PooledConnection and PoolState, let’s take a look at how PooledDataSource gets the connection. Getting the logic for connection In the PooledDataSource#getConnection method, the getConnection method is just a shell, and the specific call logic is in the popConnection method. Let's take a look (I only listed the important logic)

public Connection getConnection() throws SQLException {
  return popConnection((), ()).getProxyConnection();
}
private PooledConnection popConnection(String username, String password) throws SQLException {
  //The outermost is the while loop. If you can't get the connection, you keep trying.  while (conn == null) {
    synchronized (state) {
      if (!()) {
        //If there is an idle connection, return to the first idle connection        conn = (0);
      } else {
        //If there is no free connection        if (() &amp;lt; poolMaximumActiveConnections) {
          //If there are too few activeConnections, then new a PooledConnection          conn = new PooledConnection((), this);
        } else {
          //If there are already many activeConnections, then you can't get new          //Get the first (oldest) of the activeConnections list          PooledConnection oldestActiveConnection = (0);
          long longestCheckoutTime = ();
          if (longestCheckoutTime &amp;gt; poolMaximumCheckoutTime) {
            //If checkout time is too long, the connection is marked overdue (expired)            //Delete the oldest connection and then new a new connection            conn = new PooledConnection((), this);
            ();
          } else {
            //If checkout time is not long enough, there is no way, you can only wait, this branch will record some statistics information          }
        }
      }
      if (conn != null) {
        if (()) {
          //If you have already obtained the connection, record some statistics        } else {
          //If not obtained, statistics: bad connection +1          ++;
          localBadConnectionCount++;
          conn = null;
          //If you can't get it several times, give up and throw an exception        }
      }
    }
  }
  return conn;
}

In popConnection

  • Get the connection from the list of free connections in the PoolState object and return if there is free connection.
  • Get the connection from the active connection list of the PoolState object. If the number of connections is less than the maximum active number, new one connection returns. If not, you can only wait for other threads to release the connection before obtaining
  • Whether the connection is retrieved or not, some information counting is performed on the connection and recorded into the PoolState object. Once the time to try to get the connection exceeds the threshold, the exception will be thrown by giving up the connection.

Close the connection

See in the PooledConnection section, PooledConnection rewrites the Connection close method. The logic that is really executed when the close method of Connection is called is the pushConnection method of PooledDataSource. The logic of this code is very simple. Generally speaking, it is to delete the connection from the active list and add it to the free list. The specific implementation is as follows

protected void pushConnection(PooledConnection conn) throws SQLException {
  synchronized (state) {
    //Delete this connection from activeConnections first    (conn);
    if (()) {
      if (() &amp;lt; poolMaximumIdleConnections &amp;amp;&amp;amp; () == expectedConnectionTypeCode) {
        //If there are too few idle connections,         += ();
        if (!().getAutoCommit()) {
          ().rollback();
        }
        //new a new Connection, add to the idle list        PooledConnection newConn = new PooledConnection((), this);
        (newConn);
        //Notify other threads to grab the connection        ();
      } else {
       // Otherwise, i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e. i.e         += ();
        // Then close the connection, get the real connection object and close it        ().close();
        ();
      }
    } 
  }
}

Close process:

  • Number of idle connections <maximum number of idle connections. Create a new connection and store it in the free list of PoolState and notify other threads to grab the Connection object.
  • If the free list of PoolState is full, you can only get the real connection object and close it.

summary

  • PooledDataSource truly implements the DataSource interface. It has the meaning of connection pooling
  • PooledDataSource manages connections in connection pools through PooledConnection and PoolState
  • PooledConnection overrides the close method of the Connection object. When calling the close method of Connection, the connection will not be closed, but the connection must be returned first.
  • PoolState is the management of the status of the connection list. It has two List properties, which are stored separatelyActive connection listandIdle connection list

DataSourceFactory

Get the DataSource implementation provided by MyBatis, which needs to be passed through the factoryDataSourceFactoryinterface to obtain. Here MyBatis is usedFactory method mode. DataSourceFactory has two implementation classes. They are

  • UnpooledDataSourceFactory
  • PooledDataSourceFactory

Let's first look at the factory interface definition

public interface DataSourceFactory {
  //Set the properties, called by XMLConfigBuilder  void setProperties(Properties props);
  //Production data source, get directly  DataSource getDataSource();
}

The most important method is getDataSource, which is very intuitive. The DataSource implementation can be obtained through this method of the factory object.

UnpooledDataSourceFactory

The method of obtaining dataSource in UnpooledDataSource is very simple and intuitive.

First, the constructor new UnpooledDataSource object stored in the factory properties

Then, getDataSource can return the object directly. The specific implementation is as follows

public class UnpooledDataSourceFactory implements DataSourceFactory {
  protected DataSource dataSource;
  public UnpooledDataSourceFactory() {
     = new UnpooledDataSource();
  }
  public DataSource getDataSource() {
    return dataSource;
  }
}

PooledDataSourceFactory

PooledDataSourceFactory is interesting. If you want to be lazy, you can directly inherit it from UnpooledDataSourceFactory. You only need to new a PooledDataSource object in the constructor method and then obtain it through the getDataSource method.

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
  //The data source has been replaced with PooledDataSource  public PooledDataSourceFactory() {
     = new PooledDataSource();
  }
}

Conclusion

I personally feel that the implementation class of DataSourceFactory provided by mybatis is a bit useless. It can be said that it is still a new object. We know that factory model creation is generally more complex objects, which are used to help developers block complex details. Both mybatis implementations are just new objects.

The above is the detailed content of MyBatis basic support DataSource to implement source code analysis. For more information about MyBatis basic support DataSource, please pay attention to my other related articles!