The article directories

  • One, foreword
  • Ii. Data Sources (MyBatis three data sources and three data source factories)
    • 2.1 Three data sources of MyBatis
    • 2.2 Create data source factory and data source when initializing Mybatis
    • 2.3 DataSource delays creating a Connection object until the SQL statement is executed
  • Connection pool
    • 3.1 Do not use the UnpooledDataSource. Create a Connection each time
    • 3.2 Experiment: Why use connection pooling? Creating a Connection is too costly
    • 3.3 Use the PooledDataSource class to reduce the cost of creating a Connection
      • 3.3.1 PooledDataSource class structure: two list: idleConnections and activeConnections
      • 3.3.2 Using Connection Pools: java.sql.Connection object fetching
          • 3.3.2.1 Using a Connection pool to create a Connection object
          • 3.3.2.2 popConnection ()
          • 3.3.2.3 getProxyConnection ()
      • 3.3.3 Using Connection Pooling: Collection of java.sql.Connection objects
          • 3.3.3.1 Small experiment: When the connection pool is not used, con.close() closes the connection and releases resources
          • 3.3.3.2 Little experiment: Use a Connection pool, instead of conn.close(), to put a Connection object into a Connection pool
          • 3.3.3.3 Source code parsing (very important) : The proxy pattern implements a Connection object that calls the close() method and actually adds it to the Connection pool
  • A DataSource of JNDI type
  • 5. Interview Goldfinger
      • Interview Goldfinger 1: Mybatis three data sources and their relationships
      • Interview Goldfinger 2: Create data source factory and data source when Mybatis initializes
      • Mybatis delays creating a Connection object until the SQL statement is executed
      • Interview Goldfinger 4: Connect two lists of pools
      • Interview Goldfinger 5: Creation of the java.sql.Connection object for the Connection pool
      • Interview Goldfinger 6: Collection of java.sqL. Connection objects from the Connection pool
      • Interview Goldfinger 7: DataSource of JNDI type
  • Six, the summary

One, foreword

This paper will first describe the classification of data sources of MyBatis;

It then describes how data sources are loaded and used;

This is followed by a breakdown of UNPOOLED, POOLED, and JNDI type data source organizations;

We’ll focus on POOLED data sources and the connection pooling principles they implement.

ORM framework: Hibernate and Mybatis are both ORM frameworks, ORM framework, data source organization is a must, otherwise you need this framework why?

Ii. Data Sources (MyBatis three data sources and three data source factories)

2.1 Three data sources of MyBatis

MyBatis data source implementation is in the following four packages:

The four packages correspond to one base package and three data sources

MyBatis divides DataSource into three types:

(1) UNPOOLED does not use the connection pool data source

(2) POOLED uses the connection pool data source

(3) JNDI A data source implemented using JNDI

That is:

Accordingly, MyBatis defines UnpooledDataSource which implements java.sql.DataSource interface, and PooledDataSource class to represent UNPOOLED and POOLED data sources. As shown below:

For a DataSource of JNDI type, the value is in the JNDI context.

MyBatis DataSource is divided into three types: (1) UNPOOLED does not use the connection pool data source (2) POOLED uses the connection pool data source (3) JNDI uses the JNDI data source PooledDataSource and UnPooledDataSource both implement the java.sql.DataSource interface, and PooledDataSource holds a reference to UnPooledDataSource. When PooledDataSource needs to create a Java.sql.DataSource instance, UnPooledDataSource does so. PooledDataSource simply provides an instance of a cache connection pool.

2.2 Create data source factory and data source when initializing Mybatis

The creation of the MyBatis DataSource object occurs during the initialization of MyBatis. Let’s take a step-by-step look at how MyBatis creates the DataSource.

In the MYbatis XML configuration file, use the element to configure the data source:

  1. MyBatis creates a DataSource of the corresponding type according to the type attribute of the DataSource.

MyBatis creates PooledDataSource instance type= “UNPOOLED” : MyBatis creates PooledDataSource instance type= “UNPOOLED” : MyBatis creates PooledDataSource instance type= “JNDI” : MyBatis looks up the DataSource instance from the JNDI service and returns it

  1. By the way, MyBatis uses the factory mode to create the DataSource object, MyBatis defines the abstract factory interface: org. Apache. Ibatis. The datasource. DataSourceFactory, through its getDataSource () method returns a datasource data source:

The definition is as follows:

public interface DataSourceFactory { void setProperties(Properties props); // DataSource DataSource getDataSource(); }Copy the code

DataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource dataSource

(1) POOLED PooledDataSourceFactory (2) UNPOOLED UnpooledDataSourceFactory (3) the JNDI JndiDataSourceFactory

Its class diagram is as follows:

  1. MyBatis creates the DataSource instance and places it in the Environment object of the Configuration object for future use.

Reflection creates the data source factory when initialized,

Data sources are created by data source factories. Three data sources correspond to three data source factories. In the factory method of the factory class, use new to create the corresponding data source, as follows:



The new data source is important in the constructor of the data source factory, as long as the reflection creates a new data source factory, the data source is created.

So how is the data source factory created? Mybatis parses XML through reflection

Parse () in the XMLConfigBuilder class, environmentsElement() in parse(), dataSourceElement() in environmentsElement(),

2.3 DataSource delays creating a Connection object until the SQL statement is executed

MyBatis calls dataSource to create java.sql.Connection when we need to create SqlSession and execute SQL statement. That is, the creation of the java.SQL. Connection object is delayed until the SQL statement is executed.

For example, we have the following method to execute a simple SQL statement:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.selectList("SELECT * FROM STUDENTS");
Copy the code

Sqlsession. selectList(” SELECT * FROM STUDENTS “), MyBatis creates a java.sql.Connection object by executing the following method underneath:

protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if(level ! = null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommmit); }Copy the code

How does UnpooledDataSource implement the getConnection() method? Look at the next section.

Summary: From SQLsession.selectList to openConnection() in transactions







Connection pool

3.1 Do not use the UnpooledDataSource. Create a Connection each time

When the type attribute of the configured into UNPOOLED, MyBatis will instantiate an instance UnpooledDataSourceFactory factory first, and then through getDataSource () method returns a UnpooledDataSource instance object references, We assume the dataSource.

With UnpooledDataSource’s getConnection(), a new Connection instance object is created each time it is called.

The UnPooledDataSource getConnection() method is implemented as follows:

/* Public Connection getConnection() throws SQLException {public Connection getConnection() throws SQLException {returndoGetConnection(username, password); } private Connection doGetConnection(String username, Throws SQLException {// Encapsulating username and password into properties properties = new properties ();if(driverProperties ! = null) { props.putAll(driverProperties); }if(username ! = null) { props.setProperty("user", username);
    }
    if(password ! = null) { props.setProperty("password", password);
    }
    returndoGetConnection(props); } /* * Obtain data Connection */ private Connection doGetConnection(Properties Properties) throws SQLException {//1. Initialize the driver initializeDriver(); / / 2. Obtained from the DriverManager Connection, to get a new Connection object Connection Connection = DriverManager. GetConnection (url, properties); Configure the Connection attribute configureConnection(connection). //4. Return the Connection objectreturn connection;
}
Copy the code

As the code above shows, the UnpooledDataSource does the following:

  1. Initialize the drive: to determine whether a driver drive has been loaded into memory, if you haven’t loaded, can dynamically load driver class, and instantiate a driver object, use the DriverManager. RegisterDriver () method will be registered into memory, for later use.
  2. Create a Connection object: use the DriverManager getConnection () method to create a Connection.
  3. Configure the Connection object: set whether to automatically commit autoCommit and the isolationLevel isolationLevel.
  4. Return the Connection object.

The sequence diagram above is shown below:

Conclusion: from the above code you can see, each time we call getConnection () method, by DriverManager. GetConnection () returns the new Java SQL. Examples of the Connection.

3.2 Experiment: Why use connection pooling? Creating a Connection is too costly

  1. The cost of creating a Java.sql.Connection instance object

Let’s first look at the resource cost of creating a java.sql.Connection object. Create a Connection object by connecting to an Oracle database. See how long it takes to create a Connection object and execute an SQL statement. The code is as follows:

public static void main(String[] args) throws Exception
{
 
    String sql = "select * from hr.employees where employee_id < ? and employee_id >= ?"; PreparedStatement st = null; ResultSet rs = null; long beforeTimeOffset = -1L; Long afterTimeOffset = -1L; Long executeTimeOffset = -1l; Connection con = null; Class.forName("oracle.jdbc.driver.OracleDriver");
 
    beforeTimeOffset = new Date().getTime();
    System.out.println("before:\t" + beforeTimeOffset);
 
    con = DriverManager.getConnection("JDBC: oracle: thin: @ 127.0.0.1:1521: xe." "."louluan"."123456");
 
    afterTimeOffset = new Date().getTime();
    System.out.println("after:\t\t" + afterTimeOffset);
    System.out.println("Create Costs:\t\t" + (afterTimeOffset - beforeTimeOffset) + " ms"); st = con.prepareStatement(sql); // Set st.setint (1, 101); st.setInt(2, 0); Rs = st.executeQuery(); executeTimeOffset = new Date().getTime(); System.out.println("Exec Costs:\t\t" + (executeTimeOffset - afterTimeOffset) + " ms");
 
}
Copy the code

The execution result of the above program on my notebook is as follows:

It is clear from this that it took 250 milliseconds to create a Connection object; The SQL execution took 170 milliseconds.

Creating a Connection object took 250 milliseconds! This time for the computer can be said to be a very luxury!

This is a big price to pay just for a Connection object. Consider another case: If we operate the database once for every user request in a Web application, when there are 10,000 online users working concurrently, for the computer, 10000×250ms= 250 0000 ms= 2500 s= 41.6667 min, 41 minutes!! Using such a system for a high user base is a joke!

  1. Problem analysis:

The cost of creating a java.sql.Connection object is so high because ** the process of creating a Connection object is like establishing a communication Connection with a database. ** It takes so much time to establish a communication Connection. Often, after we create a Connection, we execute a simple SQL statement and then throw it away. This is a huge waste of resources!

  1. Solution:

For applications that need to interact with the database frequently, you can create a Connection object, and after operating on the database, you can not release the resource, but put it in memory. The next time you need to operate on the database, you can directly fetch the Connection object from memory, no need to create it again. This greatly reduces the resource cost of creating the Connection object. Since memory is also limited and precious, this puts high demands on how to effectively maintain Connection objects in memory. We call the containers that hold Connection objects in memory Connection pools. Let’s take a look at how MyBatis thread pool is implemented.

Summary: What is the underlying logic to create a Connection?

JdbcTransaction: openConnection()

PooledDataSource popConnection()

UnPooledDataSource doGetConnection()

3.3 Use the PooledDataSource class to reduce the cost of creating a Connection

3.3.1 PooledDataSource class structure: two list: idleConnections and activeConnections

Similarly, we use the getConnection() method of PooledDataSource to return the Connection object. Now let’s look at the basics:

PooledDataSource wraps java.sql.Connection into PooledConnection and puts it in a PoolState container for maintenance. MyBatis classifies PooledConnection into two states: PooledConnection objects in idle and active states are stored in two List sets of idleConnections and activeConnections respectively in PoolState:

The PoolState connection pool looks like this:

IdleConnections collection:

(1) Storage: The PooledConnection object in the idle state is placed in this collection, indicating that the PooledConnection is currently idle and unused.

PooledDataSource getConnection(); PooledDataSource getConnection();

(3) Add: When a java.sql.Connection object is used up, MyBatis will wrap it into PooledConnection object and put it into this collection.

ActiveConnections collection:

(1) Storage: The PooledConnection object in the active state is placed in the ArrayList named activeConnections, which represents the PooledConnection collection currently in use;

(2) Put: When the PooledDataSource getConnection() method is called, the PooledConnection object is first fetched from the idleConnections collection. If not, the PooledDataSource is full. PooledDataSource creates a PooledConnection, adds it to the collection, and returns.

3.3.2 Using Connection Pools: java.sql.Connection object fetching

3.3.2.1 Using a Connection pool to create a Connection object

Using a Connection pool, create a Connection object in two steps. Call popConnection() to get a PooledConnection object. Then call getProxyConnection() of the PooledConnection class to return a proxyConnection variable

Let’s look at the PooledDataSource’s getConnection() method to get a Connection object:

 public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }
 
  public Connection getConnection(String username, String password) throws SQLException {
    return popConnection(username, password).getProxyConnection();
  }
Copy the code

The above popConnection() method, in the first step, returns an available PooledConnection object from the connection pool, and in the second step, calls the getProxyConnection() method and finally returns a Conection object.

Note: This is a separate call to the two functions, not one of the other functions. The first step is to return an available PooledConnection object from the connection pool. In the second step, the getProxyConnection() method is called to finally return the Conection object

3.3.2.2 popConnection ()

Now let’s see what the popConnection() method does:

  1. PooledConnection (idle) {PooledConnection (idle) {PooledConnection (idle); Otherwise, proceed to Step 2.
  2. Check whether the active PooledConnection activeConnections is full. If not, create a new PooledConnection object, place it in the activeConnections pool, and return the PooledConnection object; Otherwise, proceed to step 3;
  3. See if the PooledConnection object that first entered the activeConnections pool has expired: If it has expired, remove this object from the activeConnections pool, create a new PooledConnection object, add it to the activeConnections, and return this object; Otherwise proceed to step 4 (Goldfinger: Step 4 if not expired).
  4. The thread waits, loop 2 steps (Goldfinger: step 4 goes to step 3, so PooledConnection activeConnections must be full, so it is necessary to find an expired one in step 3, if there is no expired one, keep looking for it until it is found)
/* * Pass a username and password, Return available PooledConnection */ 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) // loop {synchronized (state)if(state. IdleConnections. The size () > 0) {/ / have idle connections in the pool, take out the first conn. = the state idleConnections. Remove (0);if (log.isDebugEnabled())
                {
                    log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); }}else{// If not, create a new PooledConnection object // and place it in the activeConnections pool, then return the PooledConnection objectif(state. ActiveConnections. The size () < poolMaximumActiveConnections) {/ / create a new connection object conn = new PooledConnection(dataSource.getConnection(), this); @SuppressWarnings("unused")
                    //used in logging, if enabled
                    Connection realConn = conn.getRealConnection();
                    if (log.isDebugEnabled())
                    {
                        log.debug("Created connection " + conn.getRealHashCode() + "."); }}else{// Cannot create new connection // When the active connection pool is full and Cannot be created, fetch the first active connection, that is, the PooledConnection object that entered the pool first // calculate its verification time, If the verification time is greater than the maximum verification time specified by the connection pool, it is considered expired. Create a new PooledConnection PooledConnection oldestActiveConnection = using a realConnection within this PoolConnection 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()) { oldestActiveConnection.getRealConnection().rollback(); } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); oldestActiveConnection.invalidate();if (log.isDebugEnabled())
                        {
                            log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); }}else{// The first one does not expire // If it cannot be released, it Must wait for Mustwait
                        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 the PooledConnection is obtained successfully, update its informationif(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;
}
Copy the code

The corresponding processing flow chart is as follows:

Question 1: PooledConnection (conn==null); PooledConnection (conn==null); PooledConnection (conn==null); Direct return conn;

As shown above, for the PooledDataSource’s getConnection() method, the popConnection() method of the PooledDataSource class returns a PooledConnection object, PooledConnection’s getProxyConnection() is then called to return the Connection object.

3.3.2.3 getProxyConnection ()

The getProxyConnection() method is simple. It returns the proxyConnection variable directly from the class, and the rest of the operations use the proxyConnection variable instead of the realConnection variable

If you use a Connection pool, you can make sure that when you create a Connection object, you get a proxyConnection variable instead of using a realConnection variable, and the rest of the operation will use a proxyConnection variable. All methods in the proxyConnection variable are called invoke using reflection, and the underlying is done via realConnection. The only exception is that conn.close() is called instead of being closed, instead of being put into the connection pool. As follows: public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // When the call is closed, Reclaim this Connection to PooledDataSource if (close.hashcode () == methodName.hashcode () && close.equals (methodName)) { dataSource.pushConnection(this); // When the call is closed, put the connection pool return null; // Invoke (realConnection, args); // Invoke (realConnection, args); // Invoke (realConnection, args); // Only close() special handles good try {if (! Object.class.equals(method.getDeclaringClass())) { checkConnection(); } return method.invoke(realConnection, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); }}}

3.3.3 Using Connection Pooling: Collection of java.sql.Connection objects

3.3.3.1 Small experiment: When the connection pool is not used, con.close() closes the connection and releases resources

Con.close () closes the connection and frees resources when connection pooling is not in use

When we run out of Connection objects in our program and do not use the database Connection pool, we usually call connection.close() to close the Connection and release resources. As follows:

private void test() throws ClassNotFoundException, SQLException
{
    String sql = "select * from hr.employees where employee_id < ? and employee_id >= ?";
    PreparedStatement st = null;
    ResultSet rs = null;
 
    Connection con = null;
    Class.forName("oracle.jdbc.driver.OracleDriver");
    try
    {
        con = DriverManager.getConnection("JDBC: oracle: thin: @ 127.0.0.1:1521: xe." "."louluan"."123456"); st = con.prepareStatement(sql); // Set st.setint (1, 101); st.setInt(2, 0); Rs = st.executeQuery(); Con.close (); con.close(); } catch (SQLException e) { con.close(); e.printStackTrace(); }}Copy the code
3.3.3.2 Little experiment: Use a Connection pool, instead of conn.close(), to put a Connection object into a Connection pool

Call con.close() : All resources held by the Connection object are freed, and the Connection object can no longer be used.

So, if we use Connection pooling, what if we run out of Connection objects and need to put them in the pool?

The first thought that probably pops into your head is that INSTEAD of calling con.close() when I should be calling con.close(), I’ll replace it with code that puts a Connection object into a Connection pool container!

Ok, we will implement the above idea, first define a simple connection Pool, and then rewrite the above code:

package com.foo.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Vector; /** ** a simple thread-safe connection pooling implementation, This Connection Pool is singleton * putConnection() adds Connection to the Connection Pool * getConnection() returns a Connection object */ Public class Pool {private static Vector<Connection> pool = new Vector<Connection>(); private static int MAX_CONNECTION =100; private static String DRIVER="oracle.jdbc.driver.OracleDriver";
    private static String URL = "JDBC: oracle: thin: @ 127.0.0.1:1521: xe." ";
    private static String USERNAME = "louluan";
    private static String PASSWROD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); }} public static void putConnection(Connection Connection){synchronized(pool) {synchronized(pool) {if(pool.size()<MAX_CONNECTION) { pool.add(connection); }}} /** * returns a Connection object, popping the first element if there is one in the pool; * If there are no elements in the connection Pool, create a Connection object and add it to the Pool * @return Connection
     */
    public static Connection getConnection(){
        Connection connection = null;
        synchronized(pool)
        {
            if(pool.size()>0)
            {
                connection = pool.get(0);
                pool.remove(0);
            }
            else{ connection = createConnection(); pool.add(connection); }}returnconnection; } /** * create a new Connection object */ private static ConnectioncreateConnection()
    {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(URL, USERNAME,PASSWROD);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        returnconnection; }}Copy the code
package com.foo.jdbc;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Vector;
 
public class PoolTest
{
 
    private void test() throws ClassNotFoundException, SQLException
    {
        String sql = "select * from hr.employees where employee_id < ? and employee_id >= ?";
        PreparedStatement st = null;
        ResultSet rs = null;
 
        Connection con = null;
        Class.forName("oracle.jdbc.driver.OracleDriver");
        try
        {
            con = DriverManager.getConnection("JDBC: oracle: thin: @ 127.0.0.1:1521: xe." "."louluan"."123456"); st = con.prepareStatement(sql); // Set st.setint (1, 101); st.setInt(2, 0); Rs = st.executeQuery(); Pool.putconnection (con); pool.putConnection (con); } catch (SQLException e) { e.printStackTrace(); }}}Copy the code

Conn. Close () into the Pool. PutConnection (con);

The above code simply puts the Connection object we used into the Pool. If we need a Connection object, we simply use the pool.getConnection () method to fetch it from the Pool.

Yes, the above code can do this, but there is a rather inelegant implementation: we need to manually place the Connection object into the Pool, which is a silly implementation. This also differs from the usual way of using a Connection object, which is to use it and then call the close() method to release the resource.

In keeping with the usual way of using Conneciton objects, we want the close() method to be called when Connection is finished, and the Connection resource is not actually released, but actually added to the Connection pool. Can you do that? The answer is yes. Another way to describe this requirement is to provide a mechanism that lets us know what methods the Connection object calls, so that we can customize the corresponding processing mechanism for different methods. A proxy mechanism does just that (good).

3.3.3.3 Source code parsing (very important) : The proxy pattern implements a Connection object that calls the close() method and actually adds it to the Connection pool

This is to use the proxy pattern to create a proxy object for the real Connection object, and all the methods of the proxy object are implemented by calling the corresponding method of the real Connection object. When the proxy object executes the close() method, the special treatment is not to call the close() method of the real Connection object, but to add the Connection object to the Connection pool.

MyBatis pooldatasource’s PoolState internally maintains objects of type PooledConnection. PooledConnection, on the other hand, is a wrapper around the actual database Connection java.SQl. Connection instance object.

The PooledConnection object holds a real database Connection java.sql.Connection instance object and a java.sql.Connection proxy:

It is defined in part as follows:

class PooledConnection implements InvocationHandler { //...... private PooledDataSource dataSource; // Data source reference private Connection realConnection; // Real Connection object private Connection proxyConnection; // Proxy own proxy Connection //...... }Copy the code

PooledConenction implements the InvocationHandler interface, and the proxyConnection object is a proxy object generated from it:

public PooledConnection(Connection connection, PooledDataSource dataSource) { this.hashCode = connection.hashCode(); // hashcode this.realConnection = connection; // True connection this. DataSource = dataSource; This.createdtimestamp = system.currentTimemillis (); This.lastusedtimestamp = system.currentTimemillis (); // Last modified time this.valid =true; // valid this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); // proxy connection}Copy the code

In fact, for development programmers, calling PooledDataSource getConnection () method returns is the proxyConnection object, then proxyConnection. GetProxyConnection ().

When we call any method on this proxyConnection object, we call the invoke() method inside the PooledConnection object (Goldfinger: based on reflection).

Let’s look at the invoke() method definition in the PooledConnection class:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // When the call is closed, reclaim the Connection to PooledDataSourceif(CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { dataSource.pushConnection(this); // When the call is closed, it is put into the connection poolreturn null;
    } else {  // elseMethod. Invoke (realConnection, args); // Invoke (realConnection, args); // Only close() does good try {if(! Object.class.equals(method.getDeclaringClass())) { checkConnection(); }returnmethod.invoke(realConnection, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); }}}Copy the code

From the code above you can see, when we use a pooledDataSource. GetConnection () returns the Connection object’s close () method, don’t call a real Connection close () method, Instead, you put the Connection object into the Connection pool.

A DataSource of JNDI type

MyBatis defines a JndiDataSourceFactory factory to create a DataSource generated through JNDI form.

Let’s take a look at the key code for JndiDataSourceFactory:

if(properties.containsKey(INITIAL_CONTEXT) && properties.containsKey(DATA_SOURCE)) {// Find the DataSource from the JNDI Context and return the Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT)); dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE)); }else if(properties.containsKey(DATA_SOURCE)) {// // find the DataSource from the JNDI context and return DataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE)); }Copy the code

5. Interview Goldfinger

Interview Goldfinger 1: Mybatis three data sources and their relationships

MyBatis DataSource is divided into three types: (1) UNPOOLED does not use the connection pool data source (2) POOLED uses the connection pool data source (3) JNDI uses the JNDI data source PooledDataSource and UnPooledDataSource both implement the java.sql.DataSource interface, and PooledDataSource holds a reference to UnPooledDataSource. When PooledDataSource needs to create a Java.sql.DataSource instance, UnPooledDataSource does so. PooledDataSource simply provides an instance of a cache connection pool.

Interview Goldfinger 2: Create data source factory and data source when Mybatis initializes

Reflection creates the data source factory when initialized,

Data sources are created by data source factories. Three data sources correspond to three data source factories. In the factory method of the factory class, use new to create the corresponding data source, as follows:



The new data source is important in the constructor of the data source factory, as long as the reflection creates a new data source factory, the data source is created.

So how is the data source factory created? Mybatis parses XML through reflection

Parse () in the XMLConfigBuilder class, environmentsElement() in parse(), dataSourceElement() in environmentsElement(),

Mybatis delays creating a Connection object until the SQL statement is executed

Summary: From SQLsession.selectList to openConnection() in transactions







Interview Goldfinger 4: Connect two lists of pools

The PoolState connection pool looks like this:



IdleConnections collection:

(1) StorageThe PooledConnection object is placed in this collection, indicating the PooledConnection set that is currently idle and not in use.

(2) Take out:When the PooledDataSource’s getConnection() method is called, the PooledConnection object from this collection takes precedence;

(3) Put inWhen MyBatis runs out of a java.sql.Connection object, MyBatis wraps it into a PooledConnection object in this collection.

ActiveConnections collection:

(1) StorageThe PooledConnection object in the active state is placed in an ArrayList named activeConnections, representing the PooledConnection collection currently in use;

(2) Put inWhen the PooledDataSource getConnection() method is called, the PooledConnection object is first fetched from the idleConnections collection.If not, PooledDataSource creates a PooledConnection, adds it to the PooledDataSource, and returns.

Interview Goldfinger 5: Creation of the java.sql.Connection object for the Connection pool

Using a Connection pool, create a Connection object in two steps. First call popConnection() to get a PooledConnection object (important, see 3.3.2.2 popConnection() for two queues). Then call getProxyConnection() of the PooledConnection class to return a proxyConnection variable

Interview Goldfinger 6: Collection of java.sqL. Connection objects from the Connection pool

One program handles all queries

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // When the call is closed, reclaim the Connection to PooledDataSourceif(CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { dataSource.pushConnection(this); // When the call is closed, it is put into the connection poolreturn null;
    } else {  // elseMethod. Invoke (realConnection, args); // Invoke (realConnection, args); // Only close() does good try {if(! Object.class.equals(method.getDeclaringClass())) { checkConnection(); }returnmethod.invoke(realConnection, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); }}}Copy the code

Interview Goldfinger 7: DataSource of JNDI type

MyBatis defines a JndiDataSourceFactory to create a DataSource generated through the JNDI form.

if(properties.containsKey(INITIAL_CONTEXT) && properties.containsKey(DATA_SOURCE)) {// Find the DataSource from the JNDI Context and return the Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT)); dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE)); }else if(properties.containsKey(DATA_SOURCE)) {// // find the DataSource from the JNDI context and return DataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE)); }Copy the code

Six, the summary

MyBatis data source/connection pool, done.

Play code every day, progress every day!!