MyBatis07-"General Source Code Guide: Detailed Explanation of MyBatis Source Code" Notes-datasource package

This series of articles is my notes and summary from the book "General Source Code Guide: Detailed Explanation of MyBatis Source Code
" . This book is based on MyBatis-3.5.2 version. The author of the book is Brother Yi . The link is Brother Yi's Weibo in CSDN. But among all the articles I read, there was only one that briefly introduced this book. It doesn’t reveal too much about the charm of the book. Next, I will record my learning summary. If the author thinks that I have infringed the copyright, please contact me to delete it. Thanks again to Brother Yi for providing learning materials. This explanation will accompany the entire series of articles. Respect the originality . I have purchased the revised book on WeChat Reading.
Copyright statement: This article is an original article by CSDN blogger "Architect Yi Ge" and follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement when reprinting.
Original link: https://blog.csdn.net/onlinedct/article/details/107306041

As an ORM framework, MyBatis is connected to Java business applications upwards and to the database downwards. The datasource package is the most important package involved when MyBatis interacts with the database. Through the datasource package, MyBatis will complete the acquisition of data sources and the establishment of data connections, laying a solid foundation for the execution of database operation statements.

1.Background

As a package that interacts with the database, the datasource package must involve many database-related classes. These classes are an important bridge for MyBatis to connect to specific database data. Understanding them is very helpful for understanding the source code of MyBatis.

1.1 java.sql package

java.sql is often called the JDBC core API package, which provides basic functions for Java to access data in data sources. Based on this package, functions such as passing SQL statements to the database and reading and writing data in table form from the database can be realized. java.sql provides a Driver interface as a database driver interface. Different types of database vendors only need to develop corresponding Driver implementation classes according to their own database characteristics and register them through DriverManager. In this way, different types of databases from different companies can be connected based on JDBC.
Insert image description here
Based on the java.sql package, Java programs can complete various database operations. The usual process for completing a database operation is as follows.

  1. Create a DriverManager object.
  2. Get the Connection object from the DriverManager object.
  3. Get the Statement object from the Connection object.
  4. Give the SQL statement to the Statement object for execution and obtain the returned results. The results are usually placed in the ResultSet.

1.2 javax.sql package

javax.sql is often called the JDBC extension API package. It extends the functions of the JDBC core API package and provides support for the server side. It is an important part of the Java Enterprise Edition.

For example, javax.sql provides the DataSource interface, through which you can obtain the Connection object oriented to the data source. Compared with the direct use of DriverManager to establish a connection in java.sql, it is more flexible (in fact, the DataSource interface is also implemented through DriverManager Object gets the Connection object). In addition, javax.sql also provides many features such as connection pool, statement pool, distributed transactions, etc. After extending the java.sql package using the javax.sql package, it is recommended to use DataSource to obtain the Connection object instead of using the DriverManager object directly. Therefore, the execution process of a SQL statement is as follows:

  1. Create a DataSource object.
  2. Get the Connection object from the DataSource object.
  3. Get the Statement object from the Connection object.
  4. Give the SQL statement to the Statement object for execution and obtain the returned results. The results are usually placed in the ResultSet.

1.3 DriverManager

The DriverManager interface is located in java.sql. It is a JDBC driver manager that can manage a group of JDBC drivers. An important function of DriverManager is the ability to provide a database-oriented connection object Connection. This function is provided by the getConnection method in DriverManager. When the getConnection method is called, DriverManager will try to find a suitable one among the loaded drivers, use the found driver to establish a connection to the specified database, and finally return the established connection.

DriverManager mainly has the following methods. These methods are all static methods and can be called directly without creating a DriverManager object.

  • void registerDriver: Register the given driver with DriverManager.
  • void deregisterDriver: Deletes the given driver from the DriverManager.
  • Driver getDriver: Finds a driver that matches a given URL path.
  • Enumeration getDrivers: Gets all loaded JDBC drivers accessible to the current caller.
  • Connection getConnection: Establishes a connection to the given database.

1.4 DataSource

DataSource is an interface of javax.sql. As the name suggests, it represents an actual data source and functions as a factory to provide database connections.
There are only the following two interface methods in the DataSource interface, both of which are used to obtain a Connection object.

  • getConnection(): Establish a connection from the current data source.
  • getConnection(String, String): Establish a connection from the current data source. The input parameters are the username and password of the data source.

DataSource in javax.sql is just an interface, and different databases can provide multiple implementations for it. Common implementations include the following.

  • Basic implementation: Generate the basic connection object Connection to the database.
  • Connection pool implementation: The generated Connection object can be automatically added to the connection pool.
  • Distributed transaction implementation: The generated Connection object can participate in distributed transactions.
    Because the DataSource interface can have multiple implementations, it is more flexible than directly using DriverManager to obtain the connection object Connection. In the daily development process, it is recommended to use DataSource to obtain database connections. In fact, in the specific implementation of DataSource, Connection is ultimately obtained based on DriverManager, so DataSource is just a further encapsulation of DriverManager.

1.5 Connection

The Connection interface is located in java.sql and represents a connection to a database. Based on this connection, the execution of SQL statements and the acquisition of results can be completed.
The commonly used methods in Connection are as follows.

  • Statement createStatement: Create a Statement object through which SQL statements can be sent to the database.
  • CallableStatement prepareCall: Create a CallableStatement object through which the stored procedure can be called.
  • PreparedStatement prepareStatement: Creates a PreparedStatement object through which parameterized SQL statements can be sent to the database.
  • String nativeSQL: Convert the input SQL statement into a locally available SQL statement.
  • void commit: Submit the current transaction.
  • void rollback: Roll back the current transaction.
  • void close: Close the current Connection object.
  • boolean isClosed: Query whether the current Connection object is closed.
  • boolean isValid: Query whether the current Connection is valid.
  • void setAutoCommit: Set the auto-commit mode of the current Connection object based on input parameters.
  • int getTransactionIsolation: Get the transaction isolation level of the current Connection object.
  • void setTransactionIsolation: Set the transaction isolation level of the current Connection object.
  • DatabaseMetaData getMetaData: Get all metadata information of the database connected to the current Connection object.

The above methods are mainly used to complete functions such as obtaining Statement objects and setting Connection properties. At the same time, there are transaction management methods in Connection, such as commit, rollback, etc. By calling these transaction management methods, the database can be controlled to complete corresponding transaction operations.

1.6 Statement

The Statement interface is located in java.sql. Some abstract methods defined in this interface can be used to execute static SQL statements and return results. Usually the Statement object returns a result set object ResultSet.
The main methods in the Statement interface are:

  • void addBatch: Add the given SQL commands to the SQL command list of the Statement object in batches.
  • void clearBatch: Clear the SQL command list of the Statement object.
  • int[] executeBatch: Let the database execute multiple commands in batches. If the execution is successful, an array is returned. Each element in the array represents the number of database records affected by a certain command.
  • boolean execute: Execute a SQL statement.
  • ResultSet executeQuery: Execute a SQL statement and return the result set ResultSet object.
  • int executeUpdate: Execute a given SQL statement, which may be an INSERT, UPDATE, DELETE or DDL statement, etc.
  • ResultSet getResultSet: Get the current result set ResultSet object.
  • ResultSet getGeneratedKeys: Get the primary keys generated by the current operation.
  • boolean isClosed: Gets whether this Statement object has been closed.
  • void close: Close the Statement object and release related resources.
  • Connection getConnection: Get the Connection object that generated this Statement object.
    The above methods are mainly used to complete functions such as executing SQL statements and obtaining SQL statement execution results.

2. Data source factory

The datasource package adopts the typical factory method pattern. DataSourceFactory serves as the interface for all factories, and DataSource in the javax.sql package serves as the interface for all factory products.
Insert image description here
Since it is a factory method pattern, you need to choose a specific implementation factory when using it. In the dataSourceElement method in the XMLConfigBuilder class, you can see the source code related to selecting the implementation factory:

  /**
   * 解析配置信息,获取数据源工厂
   * 被解析的配置信息示例如下:
   * <dataSource type="POOLED">
   *   <property name="driver" value="{dataSource.driver}"/>
   *   <property name="url" value="{dataSource.url}"/>
   *   <property name="username" value="${dataSource.username}"/>
   *   <property name="password" value="${dataSource.password}"/>
   * </dataSource>
   *
   * @param context 被解析的节点
   * @return 数据源工厂
   * @throws Exception
   */
  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      // 通过这里的类型判断数据源类型,例如POOLED、UNPOOLED、JNDI
      String type = context.getStringAttribute("type");
      // 获取dataSource节点下配置的property
      Properties props = context.getChildrenAsProperties();
      // 根据dataSource的type值获取相应的DataSourceFactory对象
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
      // 设置DataSourceFactory对象的属性
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

MyBatis selects the implementation factory based on the type attribute of the dataSource configured in the XML file. We can choose any implementation class of the DataSource interface as the data source factory.

2.1 JNDI

JNDI data source factory The jndi sub-package in the datasource package provides a JNDI data source factory JndiDataSourceFactory.
Before reading the source code of JndiDataSourceFactory, we first understand what JNDI is and what a JNDI data source is. JNDI (Java Naming and Directory Interface) is a Java naming and directory interface. It can provide naming and directory access interfaces for Java applications. We can understand it as a naming specification. After using this specification to name a resource and placing the resource in the environment (Context), you can lookup the corresponding resource from the environment by name.

As a resource, the data source can be named using JNDI and placed in the environment. This is the JNDI data source. Afterwards, the data source can be found simply by using the name information. For example, application servers such as Tomcat can name relevant data sources and put them into the environment when starting up, and MyBatis can find the data source from the environment through its name information. The advantage of this is that application developers only need to set the JNDI name of the data source to be searched for MyBatis, and do not need to care about the specific information (address, user name, password, etc.) and generation details of the data source.

The function of JndiDataSourceFactory is to find the specified JNDI data source from the environment. Use the string "JNDI" in the MyBatis configuration file to represent the JNDI data source.

<environment id="uat">
    <transactionManager type="JDBC"/>
    <dataSource type="JNDI" >
        <!--起始环境信息-->
        <property name="initial_context" value="java:/comp/env"/>
        <!--数据源JNDI名称-->
        <property name="data_source" value="java:comp/env/jndi/mybatis" />
        <!--以"env."开头的其他环境配置信息-->
        <property name="env.encoding" value="UTF8" />
    </dataSource>
</environment>
  • initial_context: gives the initial environment information, MyBatis will go here to find the specified data source. This value can also be left unset, then MyBatis will search for data sources in the entire environment.
  • data_source: gives the name of the data source.

The getDataSource method in JndiDataSourceFactory is only responsible for returning the DataSource object in the member variable, and the operation of finding the specified DataSource from the environment is performed in the setProperties method. In essence, JndiDataSourceFactory is not producing data sources, but is only responsible for finding data sources.

/**
   * 配置数据源属性,其中包含了数据源的查找工作
   * @param properties 属性信息
   */
  @Override
  public void setProperties(Properties properties) {
    
    
    try {
    
    
      // 初始上下文环境
      InitialContext initCtx;
      // 获取配置信息,根据配置信息初始化环境
      Properties env = getEnvProperties(properties);
      if (env == null) {
    
    
        initCtx = new InitialContext();
      } else {
    
    
        initCtx = new InitialContext(env);
      }

      // 从配置信息中获取数据源信息
      if (properties.containsKey(INITIAL_CONTEXT)
          && properties.containsKey(DATA_SOURCE)) {
    
    
        // 定位到initial_context给出的起始环境
        Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT));
        // 从起始环境中寻找指定数据源
        dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE));
      } else if (properties.containsKey(DATA_SOURCE)) {
    
    
        // 从整个环境中寻找指定数据源
        dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));
      }
    } catch (NamingException e) {
    
    
      throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);
    }
  }

  /**
   * 获取数据源
   * @return 数据源
   */
  @Override
  public DataSource getDataSource() {
    
    
    return dataSource;
  }

3 Non-pooled data sources and factories

The unpooled sub-package in the datasource package provides unpooled data source factories and unpooled data sources.

3.1 Non-pooled data source factory

UnpooledDataSourceFactory is an unpooled data source factory. Unlike JndiDataSourceFactory, which is only responsible for finding the specified data source from the environment, UnpooledDataSourceFactory under the unpooled sub-package needs to actually create a data source. However, this creation process is very simple. UnpooledDataSourceFactory creates the data source object directly in its own construction method and saves it in its own member variables.

  /**
   * UnpooledDataSourceFactory的构造方法,包含了创建数据源的操作
   */
  public UnpooledDataSourceFactory() {
    
    
    this.dataSource = new UnpooledDataSource();
  }

The setProperties method of UnpooledDataSourceFactory is responsible for setting properties for the data source in the factory. The properties set for the data source are divided into two categories: properties starting with "driver." are set to the DriverManager object contained in the data source; other properties are set to the data source itself.

  /**
   * 为数据源设置配置信息
   * @param properties 配置信息
   */
  @Override
  public void setProperties(Properties properties) {
    
    
    // 驱动的属性
    Properties driverProperties = new Properties();
    // 生成一个包含DataSource对象的元对象
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    // 设置属性
    for (Object key : properties.keySet()) {
    
    
      String propertyName = (String) key;
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
    
     // 取出以"driver."开头的配置信息
        // 记录以"driver."开头的配置信息
        String value = properties.getProperty(propertyName);
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
      } else if (metaDataSource.hasSetter(propertyName)) {
    
    
        // 通过反射为DataSource设置其他的属性
        String value = (String) properties.get(propertyName);
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
    
    
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    if (driverProperties.size() > 0) {
    
    
      // 将以"driver."开头的配置信息放入DataSource的driverProperties属性中
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

Non-pooled data sources Non-pooled data sources are the simplest data sources. They only need to open the connection every time a connection is requested and close the connection at the end of each connection. Use the string "UNPOOLED" in the MyBatis configuration file to represent non-pooled data sources.

<dataSource type="UNPOOLED">
   <!--数据库驱动-->
   <property name="driver" value="com.mysql.jdbc.Driver"/>
   <!--数据源地址-->
   <property name="url" value="jdbc:mysql://127.0.0.1:3306/yeecode"/>
   <!--数据源用户名-->
   <property name="username" value="yeecode"/>
   <!--数据源密码-->
   <property name="password" value="yeecode_passward"/>
   <!--是否自动提交-->
   <property name="autoCommit" value="true" />
   <!--默认的事务隔离级别-->
   <property name="defaultTransactionlsoltionLevel" value="1"/>
   <!--默认最长等待时间-->
   <property name="defaultNetworkTimeout" value="2000"/>
   <!-- 省略了一些其他属性 -->
</dataSource>

The properties in the UnpooledDataSource class correspond to the above configuration information one-to-one.

public class UnpooledDataSource implements DataSource {
    
    
  // 驱动加载器
  private ClassLoader driverClassLoader;
  // 驱动配置信息
  private Properties driverProperties;
  // 已经注册的所有驱动
  private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
  // 数据库驱动
  private String driver;
  // 数据源地址
  private String url;
  // 数据源用户名
  private String username;
  // 数据源密码
  private String password;
  // 是否自动提交
  private Boolean autoCommit;
  // 默认事务隔离级别
  private Integer defaultTransactionIsolationLevel;
  // 最长等待时间。发出请求后,最长等待该时间后如果数据库还没有回应,则认为失败
  private Integer defaultNetworkTimeout;

4. Pooled data sources

Pooled data sources In an application, a large number of database operations are often performed. And if the database connection Connection object is established and released every time the database is operated, it will reduce the operating efficiency of the entire program. Therefore, it is very necessary to introduce a database connection pool. A certain number of database connections are always reserved in the connection pool for use, which can be taken out when needed and put back after use. This reduces the creation and destruction of connections and improves overall efficiency.

When the Connection object is obtained from the non-pooled data source UnpooledDataSource, it is actually given by the DriverManager object inside the UnpooledDataSource object. Obviously, these connection objects do not belong to any connection pool. The pooled sub-package of the datasource package provides classes related to data source connection pooling. The PooledDataSourceFactory class inherits the UnpooledDataSourceFactory class and only overrides the construction method.

4.1 Properties of pooled data source class

The string "POOLED" is used in the MyBatis configuration file to represent the pooled data source. In the configuration of the pooled data source, in addition to the related properties in the non-pooled data source, some properties related to the connection pool are also added. . The most important are the following three properties: state, dataSource, expectedConnectionTypeCode.

public class PooledDataSource implements DataSource {
    
    
  private final PoolState state = new PoolState(this);
  // 持有一个UnpooledDataSource对象
  private final UnpooledDataSource dataSource;
  // 和连接池设置有关的配置项
  protected int poolMaximumActiveConnections = 10;
  protected int poolMaximumIdleConnections = 5;
  protected int poolMaximumCheckoutTime = 20000;
  protected int poolTimeToWait = 20000;
  protected int poolMaximumLocalBadConnectionTolerance = 3;
  protected String poolPingQuery = "NO PING QUERY SET";
  protected boolean poolPingEnabled;
  protected int poolPingConnectionsNotUsedFor;
  // 存储池子中的连接的编码,编码用("" + url + username + password).hashCode()算出来
  // 因此,整个池子中的所有连接的编码必须是一致的,里面的连接是等价的
  private int expectedConnectionTypeCode;
  1. The state attribute
    state is a PoolState object that stores all database connection and status information.
    When setting up a pooled data source, it is necessary to know the size of the connection pool. If the connection pool is set too large, there will be a large number of idle connections, resulting in a waste of resources such as memory; if the connection pool is set too small, connections will need to be created and destroyed frequently, thus reducing the efficiency of program operation.

The setting of the database connection pool size needs to be judged according to the business scenario. This judgment process needs to be supported by the operation data of the connection pool. Therefore, it is very necessary to collect statistics on the operation data of the connection pool. PooledDataSource does not use lists directly but uses PoolState objects to store all database connections for the purpose of counting connection pool operation data.

Among the properties of the PoolState class, in addition to using two lists, idleConnections and activeConnections, to store all idle connections and active connections, there are also a large number of properties used to store statistical information during the operation of the connection pool.

public class PoolState {
    
    
  // 池化数据源
  protected PooledDataSource dataSource;
  // 空闲的连接
  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  // 活动的连接
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  // 连接被取出的次数
  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;
  1. dataSource When the pooled data source does not have enough connections in the connection pool, it also needs to create new connections.
    The attribute dataSource is an UnpooledDataSource object, which is given by this attribute when a new connection needs to be created.

  2. expectedConnectionTypeCode A data source connection pool must ensure that each connection in the pool is equivalent, so as to ensure that there will be no differences every time we take a connection from the connection pool. expectedConnectionTypeCode stores the connection type code of the data source.

  /**
   * 计算该连接池中连接的类型编码
   * @param url 连接地址
   * @param username 用户名
   * @param password 密码
   * @return 类型编码
   */
  private int assembleConnectionTypeCode(String url, String username, String password) {
    
    
    return ("" + url + username + password).hashCode();
  }

This value is generated when the PooledDataSource object is created, and is then assigned to each PooledConnection taken out of the connection pool of the PooledDataSource object. When the PooledDataSource is returned to the connection pool after use, this value will be verified to ensure that the returned PooledDataSource object indeed belongs to the connection pool.

PooledConnection can be understood as a master who wants to lend things. Before lending something, he would sign his name on it; when it was returned, he would check to see if it was his signature. This will prevent someone else's mistakes from being returned to you.

4.2 Giving and withdrawing pooled connections

For the pooled data source PooledDataSource, the most important job is to give and withdraw the pooled connection.

  1. Giving a pooled connection The method for giving a pooled connection is popConnection
  /**
   * 从池化数据源中给出一个连接
   * @param username 用户名
   * @param password 密码
   * @return 池化的数据库连接
   * @throws SQLException
   */
  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) {
    
    
      // 给state加同步锁
      synchronized (state) {
    
    
        if (!state.idleConnections.isEmpty()) {
    
     // 池中存在空闲连接
          // 左移操作,取出第一个连接
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
    
    
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
    
     // 池中没有空余连接
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
    
     // 池中还有空余位置
            // 可以创建新连接,也是通过DriverManager.getConnection拿到的连接
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
    
    
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {
    
     // 连接池已满,不能创建新连接
            // 找到借出去最久的连接
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            // 查看借出去最久的连接已经被借了多久
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
    
     // 借出时间超过设定的借出时长
              // 声明该连接超期不还
              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 {
    
     // 借出去最久的连接,并未超期
              // 继续等待,等待有连接归还到连接池
              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 + 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;
  }

Because the code above is lengthy, we can summarize the process using pseudocode.

During the source code reading process, there are many methods that can help us sort out the execution process of the source code, such as flow charts, pseudocode, timing diagrams, etc. They allow us to get rid of complicated details and grasp the main thread of the entire program execution. Among these methods, pseudocode has the characteristics of being simple to write and highly consistent with the source code, which allows us to focus more on the source code itself without being limited to irrelevant processes such as typesetting and drawing. Therefore, it is recommended that you use pseudocode to sort out the source code process.

Insert image description here

  1. The method to reclaim the pooled connection is the pushConnection method
  /**
   * 收回一个连接
   * @param conn 连接
   * @throws SQLException
   */
  protected void pushConnection(PooledConnection conn) throws SQLException {
    
    
    synchronized (state) {
    
    
      // 将该连接从活跃连接中删除
      state.activeConnections.remove(conn);
      if (conn.isValid()) {
    
     // 当前连接是可用的
        // 判断连接池未满 + 该连接确实属于该连接池
        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++;
      }
    }
  }

Pseudocode:
Insert image description here
3. Equivalence of connections in pooled data sources.
The connection pool of a data source must ensure that each connection in the pool is equivalent. PooledDataSource ensures this by encoding the data source connection type stored in expectedConnectionTypeCode. a little. PooledDataSource writes the encoding to the connection each time it is given, and verifies the encoding when it withdraws the connection. This prevents non-pooled connections from being put into the connection pool.

But you may still have a question: after the PooledDataSource object is established and used for a period of time, some connections will be given, and some connections are still idle in the connection pool. What will happen if we change one or more attributes of the database's driver, url, username, and password? Will there be two batches of PooledConnection objects with different properties in the connection pool?

For PooledDataSource, its Connection object is given by the UnpooledDataSource object held by the property dataSource. The attributes driver, url, username, and password exist in this UnpooledDataSource object. If you want to modify attributes such as driver, url, username, password, etc., you must call the setDriver, setUrl, setUsername, setPassword and other methods of PooledDataSource.

  public void setUrl(String url) {
    
    
    dataSource.setUrl(url);
    forceCloseAll();
  }

  public void setUsername(String username) {
    
    
    dataSource.setUsername(username);
    forceCloseAll();
  }

  public void setPassword(String password) {
    
    
    dataSource.setPassword(password);
    forceCloseAll();
  }

  /**
   * 将活动和空闲的连接全部关闭
   */
  public void forceCloseAll() {
    
    
    synchronized (state) {
    
     // 增加同步锁
      // 重新计算和更新连接类型编码
      expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
      // 依次关闭所有的活动连接
      for (int i = state.activeConnections.size(); i > 0; i--) {
    
    
        try {
    
    
          PooledConnection conn = state.activeConnections.remove(i - 1);
          conn.invalidate();

          Connection realConn = conn.getRealConnection();
          if (!realConn.getAutoCommit()) {
    
    
            realConn.rollback();
          }
          realConn.close();
        } catch (Exception e) {
    
    
          // ignore
        }
      }
      // 依次关闭所有的空闲连接
      for (int i = state.idleConnections.size(); i > 0; i--) {
    
    
        try {
    
    
          PooledConnection conn = state.idleConnections.remove(i - 1);
          conn.invalidate();

          Connection realConn = conn.getRealConnection();
          if (!realConn.getAutoCommit()) {
    
    
            realConn.rollback();
          }
          realConn.close();
        } catch (Exception e) {
    
    
          // ignore
        }
      }
    }
    if (log.isDebugEnabled()) {
    
    
      log.debug("PooledDataSource forcefully closed/removed all connections.");
    }
  }

It is found that the setDriver, setUrl, setUsername, setPassword and other methods all call the forceCloseAll method.
In the forceCloseAll method, all idle connections and active connections will be closed. Therefore, if one or more attributes of the driver, url, username, and password of the database are changed after the PooledDataSource object is established and used for a period of time, all active and idle connections will be closed. There will not be two batches of PooledConnection objects with different properties in the connection pool. This mechanism ensures that connections in pooled data sources are always equivalent.

4.3 Pooled connections

When we want to close a non-pooled database connection, the connection will actually be closed; and when we want to close a pooled connection, it should not really be closed, but should put itself back into the connection pool. Because of this, the database connection obtained through PooledDataSource cannot be an ordinary Connection class object.

There is a PooledConnection class in the pooled sub-package, which is a proxy class for the ordinary Connection class. One of its important tasks is to modify the behavior of the close method of the Connection class. The proxy filters out the closing method of the Connection object and replaces it with an operation of returning it to the connection pool instead of actually closing the connection.

The PooledConnection class inherits the InvocationHandler interface and becomes a dynamic proxy class, directly view its invoke method.

  /**
   * 代理方法
   * @param proxy 代理对象,未用
   * @param method 当前执行的方法
   * @param args 当前执行的方法的参数
   * @return 方法的返回值
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    // 获取方法名
    String methodName = method.getName();
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
    
     // 如果调用了关闭方法
      // 那么把Connection返回给连接池,而不是真正的关闭
      dataSource.pushConnection(this);
      return null;
    }
    try {
    
    
      // 校验连接是否可用
      if (!Object.class.equals(method.getDeclaringClass())) {
    
    
        checkConnection();
      }
      // 用真正的连接去执行操作
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
    
    
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

5. On data source factory

When reading the source code of the DataSourceFactory implementation class, we may be vaguely aware that they are not typical factories. Typically, typical factory workflow.
Insert image description here
The products of the factory are produced in the last stage, and multiple products can be produced by continuously calling the last stage. The products of JndiDataSourceFactory are generated during the factory product attribute setting phase, and the products of UnpooledDataSourceFactory are generated during the factory initialization phase. The workflow of the factory in the datasource package:
Insert image description here
The factory process will bring about the following problems.

  • Setting the attributes of factory products will cause products that have been shipped out of the factory to be affected. For example, after getting the DataSource object by calling the getDataSource method, calling the setProperties method on the factory will affect the DataSource object that has been obtained.
  • Obtaining factory products multiple times but only getting the same product. For example, calling the getDataSource method multiple times will get the same object, which becomes a singleton mode.

Guess you like

Origin blog.csdn.net/d495435207/article/details/130909239