Mybaits 源碼解析 (七)----- Select 語句的執行過程分析(下篇)全網最詳細,沒有之一

咱們上篇文章講到了查詢方法裏面的doQuery方法,這裏面就是調用JDBC的API了,其中的邏輯比較複雜,咱們這邊文章來說,先看看咱們上篇文章分析的地方html

SimpleExecutorjava

 1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4         Configuration configuration = ms.getConfiguration();
 5         // 建立 StatementHandler
 6         StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 7         // 建立 Statement
 8         stmt = prepareStatement(handler, ms.getStatementLog());  9         // 執行查詢操做
10         return handler.<E>query(stmt, resultHandler); 11     } finally {
12         // 關閉 Statement
13         closeStatement(stmt);
14     }
15 }

上篇文章咱們分析完了第6行代碼,在第6行處咱們建立了一個PreparedStatementHandler,咱們要接着第8行代碼開始分析,也就是建立 Statement,先不忙着分析,咱們先來回顧一下 ,咱們之前是怎麼使用jdbc的mysql

jdbcsql

public class Login {
    /**
     *    第一步,加載驅動,建立數據庫的鏈接
     *    第二步,編寫sql
     *    第三步,須要對sql進行預編譯
     *    第四步,向sql裏面設置參數
     *    第五步,執行sql
     *    第六步,釋放資源 
     * @throws Exception 
     */
     
    public static final String URL = "jdbc:mysql://localhost:3306/chenhao";
    public static final String USER = "liulx";
    public static final String PASSWORD = "123456";
    public static void main(String[] args) throws Exception {
        login("lucy","123");
    }
    
    public static void login(String username , String password) throws Exception{
        Connection conn = null; 
        PreparedStatement psmt = null;
        ResultSet rs = null;
        try {
            //加載驅動程序
            Class.forName("com.mysql.jdbc.Driver");
            //得到數據庫鏈接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
            //編寫sql
            String sql = "select * from user where name =? and password = ?";//問號至關於一個佔位符
            //對sql進行預編譯
            psmt = conn.prepareStatement(sql);
            //設置參數
            psmt.setString(1, username);
            psmt.setString(2, password);
            //執行sql ,返回一個結果集
            rs = psmt.executeQuery();
            //輸出結果
            while(rs.next()){
                System.out.println(rs.getString("user_name")+" 年齡:"+rs.getInt("age"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //釋放資源
            conn.close();
            psmt.close();
            rs.close();
        }
    }
}

上面代碼中註釋已經很清楚了,咱們來看看mybatis中是怎麼和數據庫打交道的。數據庫

SimpleExecutor數組

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 獲取數據庫鏈接
    Connection connection = getConnection(statementLog); // 建立 Statement,
    stmt = handler.prepare(connection, transaction.getTimeout()); // 爲 Statement 設置參數
 handler.parameterize(stmt); return stmt;
}

在上面的代碼中咱們終於看到了和jdbc相關的內容了,大概分爲下面三個步驟:緩存

  1. 獲取數據庫鏈接
  2. 建立PreparedStatement
  3. 爲PreparedStatement設置運行時參數

咱們先來看看獲取數據庫鏈接,跟進代碼看看安全

BaseExecutor服務器

protected Connection getConnection(Log statementLog) throws SQLException {
    //經過transaction來獲取Connection
    Connection connection = this.transaction.getConnection();
    return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}

咱們看到是經過Executor中的transaction屬性來獲取Connection,那咱們就先來看看transaction,根據前面的文章中的配置 <transactionManager type="jdbc"/>,則MyBatis會建立一個JdbcTransactionFactory.class 實例,Executor中的transaction是一個JdbcTransaction.class 實例,其實現Transaction接口,那咱們先來看看Transactionmybatis

JdbcTransaction

咱們先來看看其接口Transaction

Transaction

public interface Transaction {
    //獲取數據庫鏈接
    Connection getConnection() throws SQLException;
    //提交事務
    void commit() throws SQLException;
    //回滾事務
    void rollback() throws SQLException;
    //關閉事務
    void close() throws SQLException;
    //獲取超時時間
    Integer getTimeout() throws SQLException;
}

接着咱們看看其實現類JdbcTransaction

JdbcTransaction

public class JdbcTransaction implements Transaction {
  
  private static final Log log = LogFactory.getLog(JdbcTransaction.class);
  
  //數據庫鏈接
  protected Connection connection; //數據源信息
  protected DataSource dataSource; //隔離級別
  protected TransactionIsolationLevel level;
  //是否爲自動提交
  protected boolean autoCommmit;
  
  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommmit = desiredAutoCommit;
  }
  
  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }
  
  public Connection getConnection() throws SQLException {
    //若是事務中不存在connection,則獲取一個connection並放入connection屬性中 //第一次確定爲空
    if (connection == null) { openConnection(); } //若是事務中已經存在connection,則直接返回這個connection
    return connection;
  }
  
    /**
     * commit()功能 
     * @throws SQLException
     */
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      //使用connection的commit()
 connection.commit();
    }
  }
  
    /**
     * rollback()功能 
     * @throws SQLException
     */
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      //使用connection的rollback()
 connection.rollback();
    }
  }
  
    /**
     * close()功能 
     * @throws SQLException
     */
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      //使用connection的close()
 connection.close();
    }
  }
  
  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    //經過dataSource來獲取connection,並設置到transaction的connection屬性中
    connection = dataSource.getConnection(); if (level != null) { //經過connection設置事務的隔離級別
 connection.setTransactionIsolation(level.getLevel()); } //設置事務是否自動提交
 setDesiredAutoCommit(autoCommmit);
  }
  
  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
        if (this.connection.getAutoCommit() != desiredAutoCommit) {
            if (log.isDebugEnabled()) {
                log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + this.connection + "]");
            }
            //經過connection設置事務是否自動提交
            this.connection.setAutoCommit(desiredAutoCommit);
        }

    } catch (SQLException var3) {
        throw new TransactionException("Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: " + desiredAutoCommit + ".  Cause: " + var3, var3);
    }
  }
  
}

咱們看到JdbcTransaction中有一個Connection屬性和dataSource屬性,使用connection來進行提交、回滾、關閉等操做,也就是說JdbcTransaction其實只是在jdbc的connection上面封裝了一下,實際使用的其實仍是jdbc的事務。咱們看看getConnection()方法

//數據庫鏈接
protected Connection connection;
//數據源信息
protected DataSource dataSource;

public Connection getConnection() throws SQLException {
//若是事務中不存在connection,則獲取一個connection並放入connection屬性中
//第一次確定爲空
if (connection == null) {
  openConnection();
}
//若是事務中已經存在connection,則直接返回這個connection
return connection;
}

protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
  log.debug("Opening JDBC Connection");
}
//經過dataSource來獲取connection,並設置到transaction的connection屬性中
connection = dataSource.getConnection();
if (level != null) {
  //經過connection設置事務的隔離級別
  connection.setTransactionIsolation(level.getLevel());
}
//設置事務是否自動提交
setDesiredAutoCommit(autoCommmit);
}

先是判斷當前事務中是否存在connection,若是存在,則直接返回connection,若是不存在則經過dataSource來獲取connection,這裏咱們明白了一點,若是當前事務沒有關閉,也就是沒有釋放connection,那麼在同一個Transaction中使用的是同一個connection,咱們再來想一想,transaction是SimpleExecutor中的屬性,SimpleExecutor又是SqlSession中的屬性,那咱們能夠這樣說,同一個SqlSession中只有一個SimpleExecutor,SimpleExecutor中有一個Transaction,Transaction有一個connection。咱們來看看以下例子

public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //建立一個SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession(); try {
         EmployeeMapper employeeMapper = sqlSession.getMapper(Employee.class);
         UserMapper userMapper = sqlSession.getMapper(User.class);
         List<Employee> allEmployee = employeeMapper.getAll();
         List<User> allUser = userMapper.getAll();
         Employee employee = employeeMapper.getOne();
    } finally {
        sqlSession.close();
    }
}

咱們看到同一個sqlSession能夠獲取多個Mapper代理對象,則多個Mapper代理對象中的sqlSession引用應該是同一個,那麼多個Mapper代理對象調用方法應該是同一個Connection,直到調用close(),因此說咱們的sqlSession是線程不安全的,若是全部的業務都使用一個sqlSession,那Connection也是同一個,一個業務執行完了就將其關閉,那其餘的業務還沒執行完呢。你們明白了嗎?咱們迴歸到源碼,connection = dataSource.getConnection();,最終仍是調用dataSource來獲取鏈接,那咱們是否是要來看看dataSource呢?

咱們仍是從前面的配置文件來看<dataSource type="UNPOOLED|POOLED">,這裏有UNPOOLED和POOLED兩種DataSource,一種是使用鏈接池,一種是普通的DataSource,UNPOOLED將會創將new UnpooledDataSource()實例,POOLED將會new pooledDataSource()實例,都實現DataSource接口,那咱們先來看看DataSource接口

DataSource

public interface DataSource  extends CommonDataSource,Wrapper {
  //獲取數據庫鏈接
  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password)
    throws SQLException;

}

很簡單,只有一個獲取數據庫鏈接的接口,那咱們來看看其實現類

UnpooledDataSource

UnpooledDataSource,從名稱上便可知道,該種數據源不具備池化特性。該種數據源每次會返回一個新的數據庫鏈接,而非複用舊的鏈接。其核心的方法有三個,分別以下:

  1. initializeDriver - 初始化數據庫驅動
  2. doGetConnection - 獲取數據鏈接
  3. configureConnection - 配置數據庫鏈接

初始化數據庫驅動

看下咱們上面使用JDBC的例子,在執行 SQL 以前,一般都是先獲取數據庫鏈接。通常步驟都是加載數據庫驅動,而後經過 DriverManager 獲取數據庫鏈接。UnpooledDataSource 也是使用 JDBC 訪問數據庫的,所以它獲取數據庫鏈接的過程同樣

UnpooledDataSource

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;

    public UnpooledDataSource() {
    }

    public UnpooledDataSource(String driver, String url, String username, String password) {
        this.driver = driver;
        this.url = url;
        this.username = username;
        this.password = password;
    }

    private synchronized void initializeDriver() throws SQLException {
        // 檢測當前 driver 對應的驅動實例是否已經註冊
        if (!registeredDrivers.containsKey(driver)) {
            Class<?> driverType;
            try {
                // 加載驅動類型
                if (driverClassLoader != null) {
                    // 使用 driverClassLoader 加載驅動
                    driverType = Class.forName(driver, true, driverClassLoader);
                } else {
                    // 經過其餘 ClassLoader 加載驅動
                    driverType = Resources.classForName(driver);
                }

                // 經過反射建立驅動實例
                Driver driverInstance = (Driver) driverType.newInstance();
                /*
                 * 註冊驅動,注意這裏是將 Driver 代理類 DriverProxy 對象註冊到 DriverManager 中的,而非 Driver 對象自己。
                 */
                DriverManager.registerDriver(new DriverProxy(driverInstance));
                // 緩存驅動類名和實例,防止屢次註冊
                registeredDrivers.put(driver, driverInstance);
            } catch (Exception e) {
                throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
            }
        }
    }
    //略...
}

//DriverManager
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {

    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }
}
經過反射機制加載驅動Driver,並將其註冊到DriverManager中的一個常量集合中,供後面獲取鏈接時使用,爲何這裏是一個List呢?咱們實際開發中有可能使用到了多種數據庫類型,如Mysql、Oracle等,其驅動都是不一樣的,不一樣的數據源獲取鏈接時使用的是不一樣的驅動。
在咱們使用JDBC的時候,也沒有經過DriverManager.registerDriver(new DriverProxy(driverInstance));去註冊Driver啊,若是咱們使用的是Mysql數據源,那咱們來看Class.forName("com.mysql.jdbc.Driver");這句代碼發生了什麼
Class.forName主要是作了什麼呢?它主要是要求JVM查找並裝載指定的類。這樣咱們的類com.mysql.jdbc.Driver就被裝載進來了。並且在類被裝載進JVM的時候,它的靜態方法就會被執行。咱們來看com.mysql.jdbc.Driver的實現代碼。在它的實現裏有這麼一段代碼:
static {  
    try {  
        java.sql.DriverManager.registerDriver(new Driver());  
    } catch (SQLException E) {  
        throw new RuntimeException("Can't register driver!");  
    }  
}

 很明顯,這裏使用了DriverManager並將該類給註冊上去了。因此,對於任何實現前面Driver接口的類,只要在他們被裝載進JVM的時候註冊DriverManager就能夠實現被後續程序使用。

做爲那些被加載的Driver實現,他們自己在被裝載時會在執行的static代碼段裏經過調用DriverManager.registerDriver()來把自身註冊到DriverManager的registeredDrivers列表中。這樣後面就能夠經過獲得的Driver來取得鏈接了。

獲取數據庫鏈接

在上面例子中使用 JDBC 時,咱們都是經過 DriverManager 的接口方法獲取數據庫鏈接。咱們來看看UnpooledDataSource是如何獲取的。

UnpooledDataSource

public Connection getConnection() throws SQLException {
    return doGetConnection(username, password);
}
    
private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    if (driverProperties != null) {
        props.putAll(driverProperties);
    }
    if (username != null) {
        // 存儲 user 配置
        props.setProperty("user", username);
    }
    if (password != null) {
        // 存儲 password 配置
        props.setProperty("password", password);
    }
    // 調用重載方法
    return doGetConnection(props);
}

private Connection doGetConnection(Properties properties) throws SQLException {
    // 初始化驅動,咱們上一節已經講過了,只用初始化一次
    initializeDriver();
    // 獲取鏈接
    Connection connection = DriverManager.getConnection(url, properties);
    // 配置鏈接,包括自動提交以及事務等級
    configureConnection(connection);
    return connection;
}

private void configureConnection(Connection conn) throws SQLException {
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
        // 設置自動提交
        conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
        // 設置事務隔離級別
        conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
}

上面方法將一些配置信息放入到 Properties 對象中,而後將數據庫鏈接和 Properties 對象傳給 DriverManager 的 getConnection 方法便可獲取到數據庫鏈接。咱們來看看是怎麼獲取數據庫鏈接的

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
    // 獲取類加載器
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
      if (callerCL == null) {
        callerCL = Thread.currentThread().getContextClassLoader();
      }
    }
    // 此處省略部分代碼 
    // 這裏遍歷的是在registerDriver(Driver driver)方法中註冊的驅動對象
    // 每一個DriverInfo包含了驅動對象和其信息
    for(DriverInfo aDriver : registeredDrivers) {

      // 判斷是否爲當前線程類加載器加載的驅動類
      if(isDriverAllowed(aDriver.driver, callerCL)) {
        try {
          println("trying " + aDriver.driver.getClass().getName());

          // 獲取鏈接對象,這裏調用了Driver的父類的方法 // 若是這裏有多個DriverInfo,比喻Mysql和Oracle的Driver都註冊registeredDrivers了 // 這裏全部的Driver都會嘗試使用url和info去鏈接,哪一個鏈接上了就返回 // 會不會全部的都會鏈接上呢?不會,由於url的寫法不一樣,不一樣的Driver會判斷url是否適合當前驅動
          Connection con = aDriver.driver.connect(url, info); if (con != null) {
            // 打印鏈接成功信息
            println("getConnection returning " + aDriver.driver.getClass().getName());
            // 返回鏈接對像
            return (con);
          }
        } catch (SQLException ex) {
          if (reason == null) {
            reason = ex;
          }
        }
      } else {
        println("    skipping: " + aDriver.getClass().getName());
      }
    }  
}

代碼中循環全部註冊的驅動,而後經過驅動進行鏈接,全部的驅動都會嘗試鏈接,可是不一樣的驅動,鏈接的URL是不一樣的,如Mysql的url是jdbc:mysql://localhost:3306/chenhao,以jdbc:mysql://開頭,則其Mysql的驅動確定會判斷獲取鏈接的url符合,Oracle的也相似,咱們來看看Mysql的驅動獲取鏈接

因爲篇幅緣由,我這裏就不分析了,你們有興趣的能夠看看,最後由URL對應的驅動獲取到Connection返回,好了咱們再來看看下一種DataSource

PooledDataSource

PooledDataSource 內部實現了鏈接池功能,用於複用數據庫鏈接。所以,從效率上來講,PooledDataSource 要高於 UnpooledDataSource。可是最終獲取Connection仍是經過UnpooledDataSource,只不過PooledDataSource 提供一個存儲Connection的功能。

輔助類介紹

PooledDataSource 須要藉助兩個輔助類幫其完成功能,這兩個輔助類分別是 PoolState 和 PooledConnection。PoolState 用於記錄鏈接池運行時的狀態,好比鏈接獲取次數,無效鏈接數量等。同時 PoolState 內部定義了兩個 PooledConnection 集合,用於存儲空閒鏈接和活躍鏈接。PooledConnection 內部定義了一個 Connection 類型的變量,用於指向真實的數據庫鏈接。以及一個 Connection 的代理類,用於對部分方法調用進行攔截。至於爲何要攔截,隨後將進行分析。除此以外,PooledConnection 內部也定義了一些字段,用於記錄數據庫鏈接的一些運行時狀態。接下來,咱們來看一下 PooledConnection 的定義。

PooledConnection

class PooledConnection implements InvocationHandler {

    private static final String CLOSE = "close";
    private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};

    private final int hashCode;
    private final PooledDataSource dataSource;
    // 真實的數據庫鏈接
    private final Connection realConnection; // 數據庫鏈接代理
    private final Connection proxyConnection;
    
    // 從鏈接池中取出鏈接時的時間戳
    private long checkoutTimestamp;
    // 數據庫鏈接建立時間
    private long createdTimestamp;
    // 數據庫鏈接最後使用時間
    private long lastUsedTimestamp;
    // connectionTypeCode = (url + username + password).hashCode()
    private int connectionTypeCode;
    // 表示鏈接是否有效
    private boolean valid;

    public PooledConnection(Connection connection, PooledDataSource dataSource) {
        this.hashCode = connection.hashCode();
        this.realConnection = connection;
        this.dataSource = dataSource;
        this.createdTimestamp = System.currentTimeMillis();
        this.lastUsedTimestamp = System.currentTimeMillis();
        this.valid = true;
        // 建立 Connection 的代理類對象
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...}
    
    // 省略部分代碼
}

下面再來看看 PoolState 的定義。

PoolState 

public class PoolState {

    protected PooledDataSource dataSource; // 空閒鏈接列表
    protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); // 活躍鏈接列表
    protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>(); // 從鏈接池中獲取鏈接的次數
    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;
}

你們記住上面的空閒鏈接列表和活躍鏈接列表

獲取鏈接

前面已經說過,PooledDataSource 會將用過的鏈接進行回收,以即可以複用鏈接。所以從 PooledDataSource 獲取鏈接時,若是空閒連接列表裏有鏈接時,可直接取用。那若是沒有空閒鏈接怎麼辦呢?此時有兩種解決辦法,要麼建立新鏈接,要麼等待其餘鏈接完成任務。

PooledDataSource

public class PooledDataSource implements DataSource {
    private static final Log log = LogFactory.getLog(PooledDataSource.class);
    //這裏有輔助類PoolState
    private final PoolState state = new PoolState(this);
    //還有一個UnpooledDataSource屬性,其實真正獲取Connection是由UnpooledDataSource來完成的
    private final UnpooledDataSource dataSource;
    protected int poolMaximumActiveConnections = 10;
    protected int poolMaximumIdleConnections = 5;
    protected int poolMaximumCheckoutTime = 20000;
    protected int poolTimeToWait = 20000;
    protected String poolPingQuery = "NO PING QUERY SET";
    protected boolean poolPingEnabled = false;
    protected int poolPingConnectionsNotUsedFor = 0;
    private int expectedConnectionTypeCode;
    
    public PooledDataSource() {
        this.dataSource = new UnpooledDataSource();
    }
    
    public PooledDataSource(String driver, String url, String username, String password) {
        //構造器中建立UnpooledDataSource對象
        this.dataSource = new UnpooledDataSource(driver, url, username, password);
    }
    
    public Connection getConnection() throws SQLException {
        return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();
    }
    
    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) {
            synchronized (state) {
                // 檢測空閒鏈接集合(idleConnections)是否爲空
                if (!state.idleConnections.isEmpty()) {
                    // idleConnections 不爲空,表示有空閒鏈接可使用,直接從空閒鏈接集合中取出一個鏈接
                    conn = state.idleConnections.remove(0);
                } else {
                    /*
                     * 暫無空閒鏈接可用,但若是活躍鏈接數還未超出限制
                     *(poolMaximumActiveConnections),則可建立新的鏈接
                     */
                    if (state.activeConnections.size() < poolMaximumActiveConnections) {
                        // 建立新鏈接,看到沒,仍是經過dataSource獲取鏈接,也就是UnpooledDataSource獲取鏈接
                        conn = new PooledConnection(dataSource.getConnection(), this);
                    } 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) {...}
                            }
                            /*
                             * 建立一個新的 PooledConnection,注意,
                             * 此處複用 oldestActiveConnection 的 realConnection 變量
                             */
                            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                            /*
                             * 複用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的 
                             * createdTimestamp 用於記錄 Connection 的建立時間,而非 PooledConnection 
                             * 的建立時間。因此這裏要複用原鏈接的時間信息。
                             */
                            conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                            conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());

                            // 設置鏈接爲無效狀態
                            oldestActiveConnection.invalidate();
                            
                        } else {// 運行時間最長的鏈接並未超時
                            try {
                                if (!countedWait) {
                                    state.hadToWaitCount++;
                                    countedWait = true;
                                }
                                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 {
                        // 鏈接無效,此時累加無效鏈接相關的統計字段
                        state.badConnectionCount++;
                        localBadConnectionCount++;
                        conn = null;
                        if (localBadConnectionCount > (poolMaximumIdleConnections
                            + poolMaximumLocalBadConnectionTolerance)) {
                            throw new SQLException(...);
                        }
                    }
                }
            }

        }
        if (conn == null) {
            throw new SQLException(...);
        }

        return conn;
    }
}

從鏈接池中獲取鏈接首先會遇到兩種狀況:

  1. 鏈接池中有空閒鏈接
  2. 鏈接池中無空閒鏈接

對於第一種狀況,把鏈接取出返回便可。對於第二種狀況,則要進行細分,會有以下的狀況。

  1. 活躍鏈接數沒有超出最大活躍鏈接數
  2. 活躍鏈接數超出最大活躍鏈接數

對於上面兩種狀況,第一種狀況比較好處理,直接建立新的鏈接便可。至於第二種狀況,須要再次進行細分。

  1. 活躍鏈接的運行時間超出限制,即超時了
  2. 活躍鏈接未超時

對於第一種狀況,咱們直接將超時鏈接強行中斷,並進行回滾,而後複用部分字段從新建立 PooledConnection 便可。對於第二種狀況,目前沒有更好的處理方式了,只能等待了。

回收鏈接

相比於獲取鏈接,回收鏈接的邏輯要簡單的多。回收鏈接成功與否只取決於空閒鏈接集合的狀態,所需處理狀況不多,所以比較簡單。

咱們仍是來看看

public Connection getConnection() throws SQLException {
    return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();
}

返回的是PooledConnection的一個代理類,爲何不直接使用PooledConnection的realConnection呢?咱們能夠看下PooledConnection這個類

class PooledConnection implements InvocationHandler {

 很熟悉是吧,標準的代理類用法,看下其invoke方法

PooledConnection

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 重點在這裏,若是調用了其close方法,則實際執行的是將鏈接放回鏈接池的操做
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
        dataSource.pushConnection(this); return null;
    } else {
        try {
            if (!Object.class.equals(method.getDeclaringClass())) {
                // issue #579 toString() should never fail
                // throw an SQLException instead of a Runtime
                checkConnection();
            }
            // 其餘的操做都交給realConnection執行
            return method.invoke(realConnection, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
}

那咱們來看看pushConnection作了什麼

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
                PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this); state.idleConnections.add(newConn); // 複用時間信息
                newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());

                // 將原鏈接置爲無效狀態
                conn.invalidate();

                // 通知等待的線程
 state.notifyAll();
                
            } else {// 空閒鏈接集合已滿
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                // 回滾未提交的事務
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }

                // 關閉數據庫鏈接
                conn.getRealConnection().close();
                conn.invalidate();
            }
        } else {
            state.badConnectionCount++;
        }
    }
}

先將鏈接從活躍鏈接集合中移除,若是空閒集合未滿,此時複用原鏈接的字段信息建立新的鏈接,並將其放入空閒集合中便可;若空閒集合已滿,此時無需回收鏈接,直接關閉便可。

鏈接池總以爲很神祕,但仔細分析完其代碼以後,也就沒那麼神祕了,就是將鏈接使用完以後放到一個集合中,下面再獲取鏈接的時候首先從這個集合中獲取。  還有PooledConnection的代理模式的使用,值得咱們學習

好了,咱們已經獲取到了數據庫鏈接,接下來要建立PrepareStatement了,咱們上面JDBC的例子是怎麼獲取的? psmt = conn.prepareStatement(sql);,直接經過Connection來獲取,而且把sql傳進去了,咱們看看Mybaits中是怎麼建立PrepareStatement的

建立PreparedStatement 

PreparedStatementHandler

stmt = handler.prepare(connection, transaction.getTimeout());

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    Statement statement = null;
    try {
        // 建立 Statement
        statement = instantiateStatement(connection);
        // 設置超時和 FetchSize
        setStatementTimeout(statement, transactionTimeout);
        setFetchSize(statement);
        return statement;
    } catch (SQLException e) {
        closeStatement(statement);
        throw e;
    } catch (Exception e) {
        closeStatement(statement);
        throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
}

protected Statement instantiateStatement(Connection connection) throws SQLException {
    //獲取sql字符串,好比"select * from user where id= ?"
    String sql = boundSql.getSql(); // 根據條件調用不一樣的 prepareStatement 方法建立 PreparedStatement
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
        String[] keyColumnNames = mappedStatement.getKeyColumns();
        if (keyColumnNames == null) {
            //經過connection獲取Statement,將sql語句傳進去
            return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
        } else {
            return connection.prepareStatement(sql, keyColumnNames);
        }
    } else if (mappedStatement.getResultSetType() != null) {
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
        return connection.prepareStatement(sql);
    }
}

看到沒和jdbc的形式如出一轍,咱們具體來看看connection.prepareStatement作了什麼

 1 public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
 2        
 3     boolean canServerPrepare = true;
 4 
 5     String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql) : sql;
 6 
 7     if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {
 8         canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
 9     }
10 
11     if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {
12         canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
13     }
14 
15     if (this.useServerPreparedStmts && canServerPrepare) {
16         if (this.getCachePreparedStatements()) {
17             ......
18         } else {
19             try {
20                 //這裏使用的是ServerPreparedStatement建立PreparedStatement
21                 pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency); 22 
23                 pStmt.setResultSetType(resultSetType);
24                 pStmt.setResultSetConcurrency(resultSetConcurrency);
25             } catch (SQLException sqlEx) {
26                 // Punt, if necessary
27                 if (getEmulateUnsupportedPstmts()) {
28                     pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
29                 } else {
30                     throw sqlEx;
31                 }
32             }
33         }
34     } else {
35         pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
36     }
37 }

咱們只用看最關鍵的第21行代碼,使用ServerPreparedStatement的getInstance返回一個PreparedStatement,其實本質上ServerPreparedStatement繼承了PreparedStatement對象,咱們看看其構造方法

protected ServerPreparedStatement(ConnectionImpl conn, String sql, String catalog, int resultSetType, int resultSetConcurrency) throws SQLException {
    //略...

    try {
        this.serverPrepare(sql);
    } catch (SQLException var10) {
        this.realClose(false, true);
        throw var10;
    } catch (Exception var11) {
        this.realClose(false, true);
        SQLException sqlEx = SQLError.createSQLException(var11.toString(), "S1000", this.getExceptionInterceptor());
        sqlEx.initCause(var11);
        throw sqlEx;
    }
    //略...

}

繼續調用this.serverPrepare(sql);

public class ServerPreparedStatement extends PreparedStatement {
    //存放運行時參數的數組
    private ServerPreparedStatement.BindValue[] parameterBindings; //服務器預編譯好的sql語句返回的serverStatementId
    private long serverStatementId; private void serverPrepare(String sql) throws SQLException {
        synchronized(this.connection.getMutex()) {
            MysqlIO mysql = this.connection.getIO();
            try {
                //向sql服務器發送了一條PREPARE指令
                Buffer prepareResultPacket = mysql.sendCommand(MysqlDefs.COM_PREPARE, sql, (Buffer)null, false, characterEncoding, 0); //記錄下了預編譯好的sql語句所對應的serverStatementId
                this.serverStatementId = prepareResultPacket.readLong(); this.fieldCount = prepareResultPacket.readInt();
                //獲取參數個數,比喻 select * from user where id= ?and name = ?,其中有兩個?,則這裏返回的參數個數應該爲2
                this.parameterCount = prepareResultPacket.readInt();
                this.parameterBindings = new ServerPreparedStatement.BindValue[this.parameterCount];

                for(int i = 0; i < this.parameterCount; ++i) { //根據參數個數,初始化數組
                    this.parameterBindings[i] = new ServerPreparedStatement.BindValue(); } 
            } catch (SQLException var16) {
                throw sqlEx;
            } finally {
                this.connection.getIO().clearInputStream();
            }

        }
    }
}
ServerPreparedStatement繼承PreparedStatement,ServerPreparedStatement初始化的時候就向sql服務器發送了一條PREPARE指令,把SQL語句傳到mysql服務器,如select * from user where id= ?and name = ?,mysql服務器會對sql進行編譯,並保存在服務器,返回預編譯語句對應的id,並保存在
ServerPreparedStatement中,同時建立BindValue[] parameterBindings數組,後面設置參數就直接添加到此數組中。好了,此時咱們建立了一個ServerPreparedStatement並返回,下面就是設置運行時參數了

設置運行時參數到 SQL 中

咱們已經獲取到了PreparedStatement,接下來就是將運行時參數設置到PreparedStatement中,以下代碼

handler.parameterize(stmt);

JDBC是怎麼設置的呢?咱們看看上面的例子,很簡單吧

psmt = conn.prepareStatement(sql);
//設置參數
psmt.setString(1, username); psmt.setString(2, password);

咱們來看看parameterize方法

public void parameterize(Statement statement) throws SQLException {
    // 經過參數處理器 ParameterHandler 設置運行時參數到 PreparedStatement 中
    parameterHandler.setParameters((PreparedStatement) statement);
}

public class DefaultParameterHandler implements ParameterHandler {
    private final TypeHandlerRegistry typeHandlerRegistry;
    private final MappedStatement mappedStatement;
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;

    public void setParameters(PreparedStatement ps) {
        /*
         * 從 BoundSql 中獲取 ParameterMapping 列表,每一個 ParameterMapping 與原始 SQL 中的 #{xxx} 佔位符一一對應
         */ List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    // 獲取屬性名
                    String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        // 爲用戶傳入的參數 parameterObject 建立元信息對象
                        MetaObject metaObject = configuration.newMetaObject(parameterObject); // 從用戶傳入的參數中獲取 propertyName 對應的值
                        value = metaObject.getValue(propertyName);
                    }

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
                        // 由類型處理器 typeHandler 向 ParameterHandler 設置參數
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException e) {
                        throw new TypeException(...);
                    } catch (SQLException e) {
                        throw new TypeException(...);
                    }
                }
            }
        }
    }
}

首先從boundSql中獲取parameterMappings 集合,這塊你們能夠看看我前面的文章,而後遍歷獲取 parameterMapping中的propertyName ,如#{name} 中的name,而後從運行時參數parameterObject中獲取name對應的參數值,最後設置到PreparedStatement 中,咱們主要來看是如何設置參數的。也就是

typeHandler.setParameter(ps, i + 1, value, jdbcType);,這句代碼最終會向咱們例子中同樣執行,以下

public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
   ps.setString(i, parameter);
}

還記得咱們的PreparedStatement是什麼嗎?是ServerPreparedStatement,那咱們就來看看ServerPreparedStatement的setString方法

public void setString(int parameterIndex, String x) throws SQLException {
    this.checkClosed();
    if (x == null) {
        this.setNull(parameterIndex, 1);
    } else {
        //根據參數下標從parameterBindings數組總獲取BindValue
        ServerPreparedStatement.BindValue binding = this.getBinding(parameterIndex, false); this.setType(binding, this.stringTypeCode); //設置參數值
        binding.value = x;
        binding.isNull = false;
        binding.isLongData = false;
    }

}

protected ServerPreparedStatement.BindValue getBinding(int parameterIndex, boolean forLongData) throws SQLException {
    this.checkClosed();
    if (this.parameterBindings.length == 0) {
        throw SQLError.createSQLException(Messages.getString("ServerPreparedStatement.8"), "S1009", this.getExceptionInterceptor());
    } else {
        --parameterIndex;
        if (parameterIndex >= 0 && parameterIndex < this.parameterBindings.length) {
            if (this.parameterBindings[parameterIndex] == null) {
                this.parameterBindings[parameterIndex] = new ServerPreparedStatement.BindValue();
            } else if (this.parameterBindings[parameterIndex].isLongData && !forLongData) {
                this.detectedLongParameterSwitch = true;
            }

            this.parameterBindings[parameterIndex].isSet = true;
            this.parameterBindings[parameterIndex].boundBeforeExecutionNum = (long)this.numberOfExecutions;
            //根據參數下標從parameterBindings數組總獲取BindValue
            return this.parameterBindings[parameterIndex];
        } else {
            throw SQLError.createSQLException(Messages.getString("ServerPreparedStatement.9") + (parameterIndex + 1) + Messages.getString("ServerPreparedStatement.10") + this.parameterBindings.length, "S1009", this.getExceptionInterceptor());
        }
    }
}
就是根據參數下標從ServerPreparedStatement的參數數組parameterBindings中獲取BindValue對象,而後設置值,好了如今ServerPreparedStatement包含了預編譯SQL語句的Id和參數數組,最後一步即是執行SQL了。

執行查詢

執行查詢操做就是咱們文章開頭的最後一行代碼,以下

return handler.<E>query(stmt, resultHandler);

咱們來看看query是怎麼作的

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
    //直接執行ServerPreparedStatement的execute方法
    ps.execute();
    return this.resultSetHandler.handleResultSets(ps);
}

public boolean execute() throws SQLException {
    this.checkClosed();
    ConnectionImpl locallyScopedConn = this.connection;
    if (!this.checkReadOnlySafeStatement()) {
        throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") + Messages.getString("PreparedStatement.21"), "S1009", this.getExceptionInterceptor());
    } else {
        ResultSetInternalMethods rs = null;
        CachedResultSetMetaData cachedMetadata = null;
        synchronized(locallyScopedConn.getMutex()) {
            //略....
            rs = this.executeInternal(rowLimit, sendPacket, doStreaming, this.firstCharOfStmt == 'S', metadataFromCache, false); //略....
        }

        return rs != null && rs.reallyResult();
    }
}

省略了不少代碼,只看最關鍵的executeInternal

ServerPreparedStatement

protected ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, Buffer sendPacket, boolean createStreamingResultSet, boolean queryIsSelectOnly, Field[] metadataFromCache, boolean isBatch) throws SQLException {
    try {
        return this.serverExecute(maxRowsToRetrieve, createStreamingResultSet, metadataFromCache);
    } catch (SQLException var11) {
        throw sqlEx;
    } 
}

private ResultSetInternalMethods serverExecute(int maxRowsToRetrieve, boolean createStreamingResultSet, Field[] metadataFromCache) throws SQLException {
    synchronized(this.connection.getMutex()) {
        //略....
        MysqlIO mysql = this.connection.getIO();
        Buffer packet = mysql.getSharedSendPacket();
        packet.clear();
        packet.writeByte((byte)MysqlDefs.COM_EXECUTE);
        //將該語句對應的id寫入數據包
        packet.writeLong(this.serverStatementId); int i;
        //將對應的參數寫入數據包
        for(i = 0; i < this.parameterCount; ++i) {
            if (!this.parameterBindings[i].isLongData) {
                if (!this.parameterBindings[i].isNull) {
                    this.storeBinding(packet, this.parameterBindings[i], mysql);
                } else {
                    nullBitsBuffer[i / 8] = (byte)(nullBitsBuffer[i / 8] | 1 << (i & 7));
                }
            }
        }
        //發送數據包,表示執行id對應的預編譯sql
        Buffer resultPacket = mysql.sendCommand(MysqlDefs.COM_EXECUTE, (String)null, packet, false, (String)null, 0); //略....
        ResultSetImpl rs = mysql.readAllResults(this,  this.resultSetType,  resultPacket, true, (long)this.fieldCount, metadataFromCache);
        //返回結果
        return rs;
    }
}

ServerPreparedStatement在記錄下serverStatementId後,對於相同SQL模板的操做,每次只是發送serverStatementId和對應的參數,省去了編譯sql的過程。 至此咱們的已經從數據庫拿到了查詢結果,可是結果是ResultSetImpl類型,咱們還須要將返回結果轉化成咱們的java對象呢,留在下一篇來說吧

相關文章
相關標籤/搜索