Mybatis源碼分析——Select語句的執(zhí)行過程分析(下)

正文

我們上篇文章講到了查詢方法里面的doQuery方法,這里面就是調(diào)用JDBC的API了,其中的邏輯比較復(fù)雜,我們這邊文章來講,先看看我們上篇文章分析的地方。

SimpleExecutor

public abstract class BaseExecutor implements Executor {

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

}


public class SimpleExecutor extends BaseExecutor {

    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 創(chuàng)建 StatementHandler
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 創(chuàng)建 Statement
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 執(zhí)行查詢操作
            return handler.<E>query(stmt, resultHandler);
        } finally {
            // 關(guān)閉 Statement
            closeStatement(stmt);
        }
    }
}

上篇文章我們分析完了StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql),在代碼中創(chuàng)建了一個PreparedStatementHandler,我們要接著stmt = prepareStatement(handler, ms.getStatementLog())開始分析,也就是創(chuàng)建 Statement,先不忙著分析,我們先來回顧一下 ,我們以前是怎么使用jdbc的。

jdbc

public class JDBCDemo {

    /**
     *    第一步,加載驅(qū)動,創(chuàng)建數(shù)據(jù)庫的連接
     *    第二步,編寫sql
     *    第三步,需要對sql進行預(yù)編譯
     *    第四步,向sql里面設(shè)置參數(shù)
     *    第五步,執(zhí)行sql
     *    第六步,釋放資源 
     * @throws Exception 
     */
     
    public static final String URL = "jdbc:mysql://localhost:3306/demo";
    public static final String USER = "root";
    public static final String PASSWORD = "123456";
    
    public static void main(String[] args) throws Exception {
        jdbcDemo("lucy","123");
    }
    
    public static void jdbcDemo(String username , String password) throws Exception{
        Connection conn = null; 
        PreparedStatement psmt = null;
        ResultSet rs = null;
        try {
            //加載驅(qū)動程序
            Class.forName("com.mysql.jdbc.Driver");
            //獲得數(shù)據(jù)庫連接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
            //編寫sql
            String sql = "select * from user where name =? and password = ?";//問號相當(dāng)于一個占位符
            //對sql進行預(yù)編譯
            psmt = conn.prepareStatement(sql);
            //設(shè)置參數(shù)
            psmt.setString(1, username);
            psmt.setString(2, password);
            //執(zhí)行sql ,返回一個結(jié)果集
            rs = psmt.executeQuery();
            //輸出結(jié)果
            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();
        }
    }
}

上面代碼中注釋已經(jīng)很清楚了,我們來看看mybatis中是怎么和數(shù)據(jù)庫打交道的。

SimpleExecutor

public class SimpleExecutor extends BaseExecutor {

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 獲取數(shù)據(jù)庫連接
        Connection connection = getConnection(statementLog);
        // 創(chuàng)建 Statement
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 為 Statement 設(shè)置參數(shù)
        handler.parameterize(stmt);
        return stmt;
    }
}

在上面的代碼中我們終于看到了和jdbc相關(guān)的內(nèi)容了,大概分為下面三個步驟:

  • 1、獲取數(shù)據(jù)庫連接。
  • 2、創(chuàng)建PreparedStatement。
  • 3、為PreparedStatement設(shè)置運行時參數(shù)。

我們先來看看獲取數(shù)據(jù)庫連接,跟進代碼看看

BaseExecutor

public abstract class BaseExecutor implements Executor {

  protected Transaction transaction;

    protected Connection getConnection(Log statementLog) throws SQLException {
        //通過transaction來獲取Connection
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) {
            return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
            return connection;
        }
    }
}

我們看到是通過Executor中的transaction屬性來獲取Connection,那我們就先來看看transaction,根據(jù)前面的文章中的配置 <transactionManager type="jdbc"/>,則MyBatis會創(chuàng)建一個JdbcTransactionFactory.class 實例,Executor中的transaction是一個JdbcTransaction.class 實例,其實現(xiàn)Transaction接口,那我們先來看看Transaction

JdbcTransaction

我們先來看看其接口Transaction

Transaction

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

接著我們看看其實現(xiàn)類JdbcTransaction

JdbcTransaction

public class JdbcTransaction implements Transaction {

    private static final Log log = LogFactory.getLog(JdbcTransaction.class);

    //數(shù)據(jù)庫連接
    protected Connection connection;
    //數(shù)據(jù)源信息
    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;
    }

    @Override
    public Connection getConnection() throws SQLException {
        //如果事務(wù)中不存在connection,則獲取一個connection并放入connection屬性中
        //第一次肯定為空
        if (connection == null) {
            openConnection();
        }
        //如果事務(wù)中已經(jīng)存在connection,則直接返回這個connection
        return connection;
    }

    /**
     * commit()功能 
     * @throws SQLException
     */
    @Override
    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
     */
    @Override
    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
     */
    @Override
    public void close() throws SQLException {
        if (connection != null) {
            resetAutoCommit();
            if (log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + connection + "]");
            }
            //使用connection的close()
            connection.close();
        }
    }

    protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
        try {
            if (connection.getAutoCommit() != desiredAutoCommit) {
                if (log.isDebugEnabled()) {
                    log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
                }
                connection.setAutoCommit(desiredAutoCommit);
            }
        } catch (SQLException e) {
            // Only a very poorly implemented driver would fail here,
            // and there's not much we can do about that.
            throw new TransactionException("Error configuring AutoCommit.  "
              + "Your driver may not support getAutoCommit() or setAutoCommit(). "
              + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
        }
    }

    protected void resetAutoCommit() {
        try {
            if (!connection.getAutoCommit()) {
                // MyBatis does not call commit/rollback on a connection if just selects were performed.
                // Some databases start transactions with select statements
                // and they mandate a commit/rollback before closing the connection.
                // A workaround is setting the autocommit to true before closing the connection.
                // Sybase throws an exception here.
                if (log.isDebugEnabled()) {
                    log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
                }
                //通過connection設(shè)置事務(wù)是否自動提交
                connection.setAutoCommit(true);
            }
        } catch (SQLException e) {
            if (log.isDebugEnabled()) {
                log.debug("Error resetting autocommit to true "
                  + "before closing the connection.  Cause: " + e);
            }
        }
    }

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

    @Override
    public Integer getTimeout() throws SQLException {
        return null;
    }

}

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

public class JdbcTransaction implements Transaction {

    //數(shù)據(jù)庫連接
    protected Connection connection;
    //數(shù)據(jù)源信息
    protected DataSource dataSource;


    @Override
    public Connection getConnection() throws SQLException {
        //如果事務(wù)中不存在connection,則獲取一個connection并放入connection屬性中
        //第一次肯定為空
        if (connection == null) {
            openConnection();
        }
        //如果事務(wù)中已經(jīng)存在connection,則直接返回這個connection
        return connection;
    }

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

}

先是判斷當(dāng)前事務(wù)中是否存在connection,如果存在,則直接返回connection,如果不存在則通過dataSource來獲取connection,這里我們明白了一點,如果當(dāng)前事務(wù)沒有關(guān)閉,也就是沒有釋放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);
    //創(chuàng)建一個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引用應(yīng)該是同一個,那么多個Mapper代理對象調(diào)用方法應(yīng)該是同一個Connection,直到調(diào)用close(),所以說我們的sqlSession是線程不安全的,如果所有的業(yè)務(wù)都使用一個sqlSession,那Connection也是同一個,一個業(yè)務(wù)執(zhí)行完了就將其關(guān)閉,那其他的業(yè)務(wù)還沒執(zhí)行完呢。大家明白了嗎?我們回歸到源碼,connection = dataSource.getConnection();,最終還是調(diào)用dataSource來獲取連接,那我們是不是要來看看dataSource呢?

我們還是從前面的配置文件來看<dataSource type="UNPOOLED|POOLED">,這里有UNPOOLED和POOLED兩種DataSource,一種是使用連接池,一種是普通的DataSource,UNPOOLED將會創(chuàng)將new UnpooledDataSource()實例,POOLED將會new pooledDataSource()實例,都實現(xiàn)DataSource接口,那我們先來看看DataSource接口

DataSource

public interface DataSource  extends CommonDataSource, Wrapper {

    //獲取數(shù)據(jù)庫連接
    Connection getConnection() throws SQLException;

    //獲取數(shù)據(jù)庫連接
    Connection getConnection(String username, String password) throws SQLException;
}  

很簡單,只有一個獲取數(shù)據(jù)庫連接的接口,那我們來看看其實現(xiàn)類

UnpooledDataSource

UnpooledDataSource,從名稱上即可知道,該種數(shù)據(jù)源不具有池化特性。該種數(shù)據(jù)源每次會返回一個新的數(shù)據(jù)庫連接,而非復(fù)用舊的連接。其核心的方法有三個,分別如下:

  • initializeDriver - 初始化數(shù)據(jù)庫驅(qū)動
  • doGetConnection - 獲取數(shù)據(jù)連接
  • configureConnection - 配置數(shù)據(jù)庫連接
初始化數(shù)據(jù)庫驅(qū)動

看下我們上面使用JDBC的例子,在執(zhí)行 SQL 之前,通常都是先獲取數(shù)據(jù)庫連接。一般步驟都是加載數(shù)據(jù)庫驅(qū)動,然后通過 DriverManager 獲取數(shù)據(jù)庫連接。UnpooledDataSource 也是使用 JDBC 訪問數(shù)據(jù)庫的,因此它獲取數(shù)據(jù)庫連接的過程一樣。

UnpooledDataSource

public class UnpooledDataSource implements DataSource {
  
    private ClassLoader driverClassLoader;
    private Properties driverProperties;
    private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();

    private String driver;
    private String url;
    private String username;
    private String password;

    private Boolean autoCommit;
    private Integer defaultTransactionIsolationLevel;

    static {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();
            registeredDrivers.put(driver.getClass().getName(), driver);
        }
    }

    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 {
        // 檢測當(dāng)前 driver 對應(yīng)的驅(qū)動實例是否已經(jīng)注冊
        if (!registeredDrivers.containsKey(driver)) {
            Class<?> driverType;
            try {
                // 加載驅(qū)動類型
                if (driverClassLoader != null) {
                    // 使用 driverClassLoader 加載驅(qū)動
                    driverType = Class.forName(driver, true, driverClassLoader);
                } else {
                    // 通過其他 ClassLoader 加載驅(qū)動
                    driverType = Resources.classForName(driver);
                }
                // DriverManager requires the driver to be loaded via the system ClassLoader.
                // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
                // 通過反射創(chuàng)建驅(qū)動實例
                Driver driverInstance = (Driver)driverType.newInstance();
                /*
                 * 注冊驅(qū)動,注意這里是將 Driver 代理類 DriverProxy 對象注冊到 DriverManager 中的,而非 Driver 對象本身。
                 */
                DriverManager.registerDriver(new DriverProxy(driverInstance));
                // 緩存驅(qū)動類名和實例,防止多次注冊
                registeredDrivers.put(driver, driverInstance);
            } catch (Exception e) {
                throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
            }
        }
    }
    
    //略......
}

public class DriverManager {

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

    public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {

        registerDriver(driver, null);
    }
    
    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }
    
}

通過反射機制加載驅(qū)動Driver,并將其注冊到DriverManager中的一個常量集合中,供后面獲取連接時使用,為什么這里是一個List呢?我們實際開發(fā)中有可能使用到了多種數(shù)據(jù)庫類型,如Mysql、Oracle等,其驅(qū)動都是不同的,不同的數(shù)據(jù)源獲取連接時使用的是不同的驅(qū)動。
在我們使用JDBC的時候,也沒有通過DriverManager.registerDriver(new DriverProxy(driverInstance));去注冊Driver啊,如果我們使用的是Mysql數(shù)據(jù)源,那我們來看Class.forName("com.mysql.jdbc.Driver");這句代碼發(fā)生了什么
Class.forName主要是做了什么呢?它主要是要求JVM查找并裝載指定的類。這樣我們的類com.mysql.jdbc.Driver就被裝載進來了。而且在類被裝載進JVM的時候,它的靜態(tài)方法就會被執(zhí)行。我們來看com.mysql.jdbc.Driver的實現(xiàn)代碼。在它的實現(xiàn)里有這么一段代碼:

static {  
    try {  
        java.sql.DriverManager.registerDriver(new Driver());  
    } catch (SQLException E) {  
        throw new RuntimeException("Can't register driver!");  
    }  
}

很明顯,這里使用了DriverManager并將該類給注冊上去了。所以,對于任何實現(xiàn)前面Driver接口的類,只要在他們被裝載進JVM的時候注冊DriverManager就可以實現(xiàn)被后續(xù)程序使用。

作為那些被加載的Driver實現(xiàn),他們本身在被裝載時會在執(zhí)行的static代碼段里通過調(diào)用DriverManager.registerDriver()來把自身注冊到DriverManager的registeredDrivers列表中。這樣后面就可以通過得到的Driver來取得連接了。

獲取數(shù)據(jù)庫連接

在上面例子中使用 JDBC 時,我們都是通過 DriverManager 的接口方法獲取數(shù)據(jù)庫連接。我們來看看UnpooledDataSource是如何獲取的。

UnpooledDataSource
public class UnpooledDataSource implements DataSource {

    @Override
    public Connection getConnection() throws SQLException {
        return doGetConnection(username, password);
    }

    @Override
    public Connection getConnection(String username, String password) 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);
        }
        // 調(diào)用重載方法
        return doGetConnection(props);
    }

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

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

上面方法將一些配置信息放入到 Properties 對象中,然后將數(shù)據(jù)庫連接和 Properties 對象傳給 DriverManager 的 getConnection 方法即可獲取到數(shù)據(jù)庫連接。我們來看看是怎么獲取數(shù)據(jù)庫連接的

public class DriverManager {

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

    @CallerSensitive
    public static Connection getConnection(String url,
        java.util.Properties info) throws SQLException {

        return (getConnection(url, info, Reflection.getCallerClass()));
    }


    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
         // 獲取類加載器
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        // 此處省略部分代碼 
        // 這里遍歷的是在registerDriver(Driver driver)方法中注冊的驅(qū)動對象
        // 每個DriverInfo包含了驅(qū)動對象和其信息
        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            // 判斷是否為當(dāng)前線程類加載器加載的驅(qū)動類
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    // 獲取連接對象,這里調(diào)用了Driver的父類的方法
                    // 如果這里有多個DriverInfo,比喻Mysql和Oracle的Driver都注冊registeredDrivers了
                    // 這里所有的Driver都會嘗試使用url和info去連接,哪個連接上了就返回
                    // 會不會所有的都會連接上呢?不會,因為url的寫法不同,不同的Driver會判斷url是否適合當(dāng)前驅(qū)動
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        // 打印連接成功信息
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        // 返回連接對像
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }
}

代碼中循環(huán)所有注冊的驅(qū)動,然后通過驅(qū)動進行連接,所有的驅(qū)動都會嘗試連接,但是不同的驅(qū)動,連接的URL是不同的,Mysql的url是jdbc:mysql://localhost:3306/demo,以jdbc:mysql://開頭,則其Mysql的驅(qū)動肯定會判斷獲取連接的url符合,Oracle的也類似。

由于篇幅原因,我這里就不分析了,大家有興趣的可以看看,最后由URL對應(yīng)的驅(qū)動獲取到Connection返回,好了我們再來看看下一種DataSource

PooledDataSource

PooledDataSource 內(nèi)部實現(xiàn)了連接池功能,用于復(fù)用數(shù)據(jù)庫連接。因此,從效率上來說,PooledDataSource 要高于 UnpooledDataSource。但是最終獲取Connection還是通過UnpooledDataSource,只不過PooledDataSource 提供一個存儲Connection的功能。

輔助類介紹

PooledDataSource 需要借助兩個輔助類幫其完成功能,這兩個輔助類分別是 PoolState 和 PooledConnection。PoolState 用于記錄連接池運行時的狀態(tài),比如連接獲取次數(shù),無效連接數(shù)量等。同時 PoolState 內(nèi)部定義了兩個 PooledConnection 集合,用于存儲空閑連接和活躍連接。PooledConnection 內(nèi)部定義了一個 Connection 類型的變量,用于指向真實的數(shù)據(jù)庫連接。以及一個 Connection 的代理類,用于對部分方法調(diào)用進行攔截。至于為什么要攔截,隨后將進行分析。除此之外,PooledConnection 內(nèi)部也定義了一些字段,用于記錄數(shù)據(jù)庫連接的一些運行時狀態(tài)。接下來,我們來看一下 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;
    // 真實的數(shù)據(jù)庫連接
    private final Connection realConnection;
    // 數(shù)據(jù)庫連接代理
    private final Connection proxyConnection;
    // 從連接池中取出連接時的時間戳
    private long checkoutTimestamp;
    // 數(shù)據(jù)庫連接創(chuàng)建時間
    private long createdTimestamp;
    // 數(shù)據(jù)庫連接最后使用時間
    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;
        // 創(chuàng)建 Connection 的代理類對象
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }
    
    // 省略部分代碼......
}

下面再來看看 PoolState 的定義。

PoolState

public class PoolState {

    protected PooledDataSource dataSource;
    // 空閑連接列表
    protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
    // 活躍連接列表
    protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
    // 從連接池中獲取連接的次數(shù)
    protected long requestCount = 0;
    // 請求連接總耗時(單位:毫秒)
    protected long accumulatedRequestTime = 0;
    // 連接執(zhí)行時間總耗時
    protected long accumulatedCheckoutTime = 0;
    // 執(zhí)行時間超時的連接數(shù)
    protected long claimedOverdueConnectionCount = 0;
    // 超時時間累加值
    protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
    // 等待時間累加值
    protected long accumulatedWaitTime = 0;
    // 等待次數(shù)
    protected long hadToWaitCount = 0;
    // 無效連接數(shù)
    protected long badConnectionCount = 0;
    
    // 省略部分代碼......
}

大家記住上面的空閑連接列表和活躍連接列表

獲取連接

前面已經(jīng)說過,PooledDataSource 會將用過的連接進行回收,以便可以復(fù)用連接。因此從 PooledDataSource 獲取連接時,如果空閑鏈接列表里有連接時,可直接取用。那如果沒有空閑連接怎么辦呢?此時有兩種解決辦法,要么創(chuàng)建新連接,要么等待其他連接完成任務(wù)。

PooledDataSource

public class PooledDataSource implements DataSource {

    private static final Log log = LogFactory.getLog(PooledDataSource.class);

    private final PoolState state = new PoolState(this);

    private final UnpooledDataSource dataSource;

    // OPTIONAL CONFIGURATION FIELDS
    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;

    private int expectedConnectionTypeCode;

    public PooledDataSource() {
        //構(gòu)造器中創(chuàng)建UnpooledDataSource對象
        dataSource = new UnpooledDataSource();
    }
    
    public PooledDataSource(UnpooledDataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return popConnection(dataSource.getUsername(), 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()) {
                    // Pool has available connection
                    // idleConnections 不為空,表示有空閑連接可以使用,直接從空閑連接集合中取出一個連接
                    conn = state.idleConnections.remove(0);
                    if (log.isDebugEnabled()) {
                        log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                    }
                } else {
                    // Pool does not have available connection
                    /*
                     * 暫無空閑連接可用,但如果活躍連接數(shù)還未超出限制
                     *(poolMaximumActiveConnections),則可創(chuàng)建新的連接
                     */
                    if (state.activeConnections.size() < poolMaximumActiveConnections) {
                        // Can create new connection
                        // 創(chuàng)建新連接,看到?jīng)],還是通過dataSource獲取連接,也就是UnpooledDataSource獲取連接
                        conn = new PooledConnection(dataSource.getConnection(), this);
                        if (log.isDebugEnabled()) {
                            log.debug("Created connection " + conn.getRealHashCode() + ".");
                        }
                    } else {
                        // Cannot create new connection
                        // 連接池已滿,不能創(chuàng)建新連接
                        // 取出運行時間最長的連接
                        PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                        // 獲取運行時長
                        long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
                        // 檢測運行時長是否超出限制,即超時
                        if (longestCheckoutTime > poolMaximumCheckoutTime) {
                            // Can claim overdue connection
                            // 累加超時相關(guān)的統(tǒng)計字段
                            state.claimedOverdueConnectionCount++;
                            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                            state.accumulatedCheckoutTime += longestCheckoutTime;
                            // 從活躍連接集合中移除超時連接
                            state.activeConnections.remove(oldestActiveConnection);
                            // 若連接未設(shè)置自動提交,此處進行回滾操作
                            if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                                try {
                                    oldestActiveConnection.getRealConnection().rollback();
                                } catch (SQLException e) {

                                    log.debug("Bad connection. Could not roll back");
                                }  
                            }
                            /*
                             * 創(chuàng)建一個新的 PooledConnection,注意,
                             * 此處復(fù)用 oldestActiveConnection 的 realConnection 變量
                             */
                            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                            /*
                             * 復(fù)用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的 
                             * createdTimestamp 用于記錄 Connection 的創(chuàng)建時間,而非 PooledConnection 
                             * 的創(chuàng)建時間。所以這里要復(fù)用原連接的時間信息。
                             */
                            conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
                            conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
                            // 設(shè)置連接為無效狀態(tài)
                            oldestActiveConnection.invalidate();
                            if (log.isDebugEnabled()) {
                                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                            }
                        } else {
                            // Must wait
                            // 運行時間最長的連接并未超時
                            try {
                                if (!countedWait) {
                                    state.hadToWaitCount++;
                                    countedWait = true;
                                }
                                if (log.isDebugEnabled()) {
                                    log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                                }
                                long wt = System.currentTimeMillis();
                                // 當(dāng)前線程進入等待狀態(tài)
                                state.wait(poolTimeToWait);
                                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                            } catch (InterruptedException e) {
                                break;
                            }
                        }
                    }
                }
                if (conn != null) {
                    // ping to server and check the connection is valid or not
                    if (conn.isValid()) {
                        if (!conn.getRealConnection().getAutoCommit()) {
                            // 進行回滾操作
                            conn.getRealConnection().rollback();
                        }
                        // 設(shè)置統(tǒng)計字段
                        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.");
                        }
                        // 連接無效,此時累加無效連接相關(guān)的統(tǒng)計字段
                        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;
    }
}

從連接池中獲取連接首先會遇到兩種情況:

  • 1、連接池中有空閑連接。
  • 2、連接池中無空閑連接。

對于第一種情況,把連接取出返回即可。對于第二種情況,則要進行細分,會有如下的情況。

  • 1、活躍連接數(shù)沒有超出最大活躍連接數(shù)。
  • 2、活躍連接數(shù)超出最大活躍連接數(shù)。

對于上面兩種情況,第一種情況比較好處理,直接創(chuàng)建新的連接即可。至于第二種情況,需要再次進行細分。

  • 1、活躍連接的運行時間超出限制,即超時了
  • 2、活躍連接未超時

對于第一種情況,我們直接將超時連接強行中斷,并進行回滾,然后復(fù)用部分字段重新創(chuàng)建 PooledConnection 即可。對于第二種情況,目前沒有更好的處理方式了,只能等待了。

回收連接

相比于獲取連接,回收連接的邏輯要簡單的多?;厥者B接成功與否只取決于空閑連接集合的狀態(tài),所需處理情況很少,因此比較簡單。

我們還是來看看

public class PooledDataSource implements DataSource {

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

返回的是PooledConnection的一個代理類,為什么不直接使用PooledConnection的realConnection呢?我們可以看下PooledConnection這個類

class PooledConnection implements InvocationHandler

很熟悉是吧,標(biāo)準(zhǔn)的代理類用法,看下其invoke方法

PooledConnection

class PooledConnection implements InvocationHandler {

    private final PooledDataSource dataSource;
    private final Connection realConnection;
    private final Connection proxyConnection;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        // 重點在這里,如果調(diào)用了其close方法,則實際執(zhí)行的是將連接放回連接池的操作
        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執(zhí)行
                return method.invoke(realConnection, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
        }
    }
}

那我們來看看pushConnection做了什么

public class PooledDataSource implements DataSource {

    private final PoolState state = new PoolState(this);

    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();
                    
                    // 回滾未提交的事務(wù)
                    if (!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }
                    
                    // 創(chuàng)建新的 PooledConnection
                    PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
                    state.idleConnections.add(newConn);
                    
                    // 復(fù)用時間信息
                    newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
                    newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
                    
                    // 將原連接置為無效狀態(tài)
                    conn.invalidate();
                    if (log.isDebugEnabled()) {
                        log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
                    }
                    
                    // 通知等待的線程
                    state.notifyAll();
                } else {
                
                // 空閑連接集合已滿
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                
                // 回滾未提交的事務(wù)
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                
                // 關(guān)閉數(shù)據(jù)庫連接
                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++;
            }
        }
    }
}

先將連接從活躍連接集合中移除,如果空閑集合未滿,此時復(fù)用原連接的字段信息創(chuàng)建新的連接,并將其放入空閑集合中即可;若空閑集合已滿,此時無需回收連接,直接關(guān)閉即可。

連接池總覺得很神秘,但仔細分析完其代碼之后,也就沒那么神秘了,就是將連接使用完之后放到一個集合中,下面再獲取連接的時候首先從這個集合中獲取。 還有PooledConnection的代理模式的使用,值得我們學(xué)習(xí)

好了,我們已經(jīng)獲取到了數(shù)據(jù)庫連接,接下來要創(chuàng)建PrepareStatement了,我們上面JDBC的例子是怎么獲取的? psmt = conn.prepareStatement(sql);,直接通過Connection來獲取,并且把sql傳進去了,我們看看Mybaits中是怎么創(chuàng)建PrepareStatement的

創(chuàng)建PreparedStatement

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

public abstract class BaseStatementHandler implements StatementHandler {

    protected BoundSql boundSql;

    @Override
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
            // 創(chuàng)建 Statement
            statement = instantiateStatement(connection);
            // 設(shè)置超時和 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 abstract Statement instantiateStatement(Connection connection) throws SQLException;
}


public class PreparedStatementHandler extends BaseStatementHandler {

    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        //獲取sql字符串,比如"select * from user where id= ?"
        String sql = boundSql.getSql();
        // 根據(jù)條件調(diào)用不同的 prepareStatement 方法創(chuàng)建 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);
        }
    }
}

看到?jīng)]和jdbc的形式一模一樣,我們具體來看看connection.prepareStatement做了什么

public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable {

    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        try {
            synchronized(this.getConnectionMutex()) {
                this.checkClosed();
                ClientPreparedStatement pStmt = null;
                boolean canServerPrepare = true;
                String nativeSql = (Boolean)this.processEscapeCodesForPrepStmts.getValue() ? this.nativeSQL(sql) : sql;
                if ((Boolean)this.useServerPrepStmts.getValue() && (Boolean)this.emulateUnsupportedPstmts.getValue()) {
                    canServerPrepare = this.canHandleAsServerPreparedStatement(nativeSql);
                }

                if ((Boolean)this.useServerPrepStmts.getValue() && canServerPrepare) {
                    if ((Boolean)this.cachePrepStmts.getValue()) {
                        synchronized(this.serverSideStatementCache) {
                            pStmt = (ClientPreparedStatement)this.serverSideStatementCache.remove(new ConnectionImpl.CompoundCacheKey(this.database, sql));
                            if (pStmt != null) {
                                ((ServerPreparedStatement)pStmt).setClosed(false);
                                ((ClientPreparedStatement)pStmt).clearParameters();
                            }

                            if (pStmt == null) {
                                try {
                                    //這里使用的是ServerPreparedStatement創(chuàng)建PreparedStatement
                                    pStmt = ServerPreparedStatement.getInstance(this.getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);
                                    if (sql.length() < (Integer)this.prepStmtCacheSqlLimit.getValue()) {
                                        ((ServerPreparedStatement)pStmt).isCacheable = true;
                                    }

                                    ((ClientPreparedStatement)pStmt).setResultSetType(resultSetType);
                                    ((ClientPreparedStatement)pStmt).setResultSetConcurrency(resultSetConcurrency);
                                } catch (SQLException var14) {
                                    if (!(Boolean)this.emulateUnsupportedPstmts.getValue()) {
                                        throw var14;
                                    }

                                    pStmt = (ClientPreparedStatement)this.clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
                                    if (sql.length() < (Integer)this.prepStmtCacheSqlLimit.getValue()) {
                                        this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);
                                    }
                                }
                            }
                        }
                    } else {
                        try {
                            pStmt = ServerPreparedStatement.getInstance(this.getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);
                            ((ClientPreparedStatement)pStmt).setResultSetType(resultSetType);
                            ((ClientPreparedStatement)pStmt).setResultSetConcurrency(resultSetConcurrency);
                        } catch (SQLException var13) {
                            if (!(Boolean)this.emulateUnsupportedPstmts.getValue()) {
                                throw var13;
                            }

                            pStmt = (ClientPreparedStatement)this.clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
                        }
                    }
                } else {
                    pStmt = (ClientPreparedStatement)this.clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
                }

                return (PreparedStatement)pStmt;
            }
        } catch (CJException var17) {
            throw SQLExceptionsMapping.translateException(var17, this.getExceptionInterceptor());
        }
    }
}

我們只用看最關(guān)鍵的代碼,使用ServerPreparedStatement的getInstance返回一個PreparedStatement,其實本質(zhì)上ServerPreparedStatement繼承了PreparedStatement對象,我們看看其構(gòu)造方法

public class ServerPreparedStatement extends ClientPreparedStatement {

    protected static ServerPreparedStatement getInstance(JdbcConnection conn, String sql, String db, int resultSetType, int resultSetConcurrency) throws SQLException {
        return new ServerPreparedStatement(conn, sql, db, resultSetType, resultSetConcurrency);
    }

    protected ServerPreparedStatement(JdbcConnection conn, String sql, String db, int resultSetType, int resultSetConcurrency) throws SQLException {
        super(conn, db);
        this.checkNullOrEmptyQuery(sql);
        String statementComment = this.session.getProtocol().getQueryComment();
        ((PreparedQuery)this.query).setOriginalSql(statementComment == null ? sql : "/* " + statementComment + " */ " + sql);
        ((PreparedQuery)this.query).setParseInfo(new ParseInfo(((PreparedQuery)this.query).getOriginalSql(), this.session, this.charEncoding));
        this.hasOnDuplicateKeyUpdate = ((PreparedQuery)this.query).getParseInfo().getFirstStmtChar() == 'I' && this.containsOnDuplicateKeyInString(sql);

        try {
            this.serverPrepare(sql);
        } catch (SQLException | CJException var8) {
            this.realClose(false, true);
            throw SQLExceptionsMapping.translateException(var8, this.exceptionInterceptor);
        }

        this.setResultSetType(resultSetType);
        this.setResultSetConcurrency(resultSetConcurrency);
    }   
}

設(shè)置運行時參數(shù)到 SQL 中

我們已經(jīng)獲取到了PreparedStatement,接下來就是將運行時參數(shù)設(shè)置到PreparedStatement中,如下代碼

handler.parameterize(stmt);

我們來看看parameterize方法

public class PreparedStatementHandler extends BaseStatementHandler {

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

public interface ParameterHandler {

  void setParameters(PreparedStatement ps)
      throws SQLException;

}


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;

    @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        
        /*
         * 從 BoundSql 中獲取 ParameterMapping 列表,每個 ParameterMapping 與原始 SQL 中的 #{xxx} 占位符一一對應(yīng)
         */
        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)) { // issue #448 ask first for additional params
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        // 為用戶傳入的參數(shù) parameterObject 創(chuàng)建元信息對象
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        // 從用戶傳入的參數(shù)中獲取 propertyName 對應(yīng)的值
                        value = metaObject.getValue(propertyName);
                    }
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
                        // 由類型處理器 typeHandler 向 ParameterHandler 設(shè)置參數(shù)
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    } catch (SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
    }
}

首先從boundSql中獲取parameterMappings 集合,這塊大家可以看看我前面的文章,然后遍歷獲取 parameterMapping中的propertyName ,如#{name} 中的name,然后從運行時參數(shù)parameterObject中獲取name對應(yīng)的參數(shù)值,最后設(shè)置到PreparedStatement 中,我們主要來看是如何設(shè)置參數(shù)的。也就是typeHandler.setParameter(ps, i + 1, value, jdbcType);,這句代碼最終會向我們例子中一樣執(zhí)行,如下

public class StringTypeHandler extends BaseTypeHandler<String> {

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

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

public class ServerPreparedStatement extends ClientPreparedStatement {

    @Override
    public void setURL(int parameterIndex, URL x) throws SQLException {
        checkClosed();

        setString(parameterIndex, x.toString());
    }
}

public class ClientPreparedStatement extends com.mysql.cj.jdbc.StatementImpl implements JdbcPreparedStatement {

    @Override
    public void setString(int parameterIndex, String x) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            ((PreparedQuery<?>) this.query).getQueryBindings().setString(getCoreParameterIndex(parameterIndex), x);
        }
    }
}


public class ServerPreparedQueryBindings extends AbstractQueryBindings<ServerPreparedQueryBindValue> {

    @Override
    public void setString(int parameterIndex, String x) {
        if (x == null) {
            setNull(parameterIndex);
        } else {
            //根據(jù)參數(shù)下標(biāo)從parameterBindings數(shù)組總獲取BindValue
            ServerPreparedQueryBindValue binding = getBinding(parameterIndex, false);
            this.sendTypesToServer.compareAndSet(false, binding.resetToType(MysqlType.FIELD_TYPE_VAR_STRING, this.numberOfExecutions));
            //設(shè)置參數(shù)值
            binding.value = x;
            binding.charEncoding = this.charEncoding;
            binding.parameterType = MysqlType.VARCHAR;
        }
    }
}


public class ServerPreparedQueryBindings extends AbstractQueryBindings<ServerPreparedQueryBindValue> {

    public ServerPreparedQueryBindValue getBinding(int parameterIndex, boolean forLongData) {

        if (this.bindValues[parameterIndex] == null) {
            //            this.bindValues[parameterIndex] = new ServerPreparedQueryBindValue();
        } else {
            if (this.bindValues[parameterIndex].isStream && !forLongData) {
                this.longParameterSwitchDetected = true;
            }
        }
         //根據(jù)參數(shù)下標(biāo)從bindValues數(shù)組中獲取BindValue
        return this.bindValues[parameterIndex];
    }
}

執(zhí)行查詢

執(zhí)行查詢操作就是我們文章開頭的最后一行代碼,如下

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

我們來看看query是怎么做的

public class PreparedStatementHandler extends BaseStatementHandler {

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


public class ClientPreparedStatement extends com.mysql.cj.jdbc.StatementImpl implements JdbcPreparedStatement {

    @Override
    public boolean execute() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {

            JdbcConnection locallyScopedConn = this.connection;

            if (!this.doPingInstead && !checkReadOnlySafeStatement()) {
                throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") + Messages.getString("PreparedStatement.21"),
                        MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
            }

            ResultSetInternalMethods rs = null;

            this.lastQueryIsOnDupKeyUpdate = false;

            if (this.retrieveGeneratedKeys) {
                this.lastQueryIsOnDupKeyUpdate = containsOnDuplicateKeyUpdateInSQL();
            }

            this.batchedGeneratedKeys = null;

            resetCancelledState();

            implicitlyCloseAllOpenResults();

            clearWarnings();

            if (this.doPingInstead) {
                doPingInstead();

                return true;
            }

            setupStreamingTimeout(locallyScopedConn);

            Message sendPacket = ((PreparedQuery<?>) this.query).fillSendPacket();

            String oldDb = null;

            if (!locallyScopedConn.getDatabase().equals(this.getCurrentDatabase())) {
                oldDb = locallyScopedConn.getDatabase();
                locallyScopedConn.setDatabase(this.getCurrentDatabase());
            }

            //
            // Check if we have cached metadata for this query...
            //
            CachedResultSetMetaData cachedMetadata = null;

            boolean cacheResultSetMetadata = locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue();
            if (cacheResultSetMetadata) {
                cachedMetadata = locallyScopedConn.getCachedMetaData(((PreparedQuery<?>) this.query).getOriginalSql());
            }

            //
            // Only apply max_rows to selects
            //
            locallyScopedConn.setSessionMaxRows(((PreparedQuery<?>) this.query).getParseInfo().getFirstStmtChar() == 'S' ? this.maxRows : -1);

            rs = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(),
                    (((PreparedQuery<?>) this.query).getParseInfo().getFirstStmtChar() == 'S'), cachedMetadata, false);

            if (cachedMetadata != null) {
                locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery<?>) this.query).getOriginalSql(), cachedMetadata, rs);
            } else {
                if (rs.hasRows() && cacheResultSetMetadata) {
                    locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery<?>) this.query).getOriginalSql(), null /* will be created */, rs);
                }
            }

            if (this.retrieveGeneratedKeys) {
                rs.setFirstCharOfQuery(((PreparedQuery<?>) this.query).getParseInfo().getFirstStmtChar());
            }

            if (oldDb != null) {
                locallyScopedConn.setDatabase(oldDb);
            }

            if (rs != null) {
                this.lastInsertId = rs.getUpdateID();

                this.results = rs;
            }

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

只看最關(guān)鍵的executeInternal方法

public class ClientPreparedStatement extends com.mysql.cj.jdbc.StatementImpl implements JdbcPreparedStatement {

    protected <M extends Message> ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, M sendPacket, boolean createStreamingResultSet,
            boolean queryIsSelectOnly, ColumnDefinition metadata, boolean isBatch) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            try {

                JdbcConnection locallyScopedConnection = this.connection;

                ((PreparedQuery<?>) this.query).getQueryBindings()
                        .setNumberOfExecutions(((PreparedQuery<?>) this.query).getQueryBindings().getNumberOfExecutions() + 1);

                ResultSetInternalMethods rs;

                CancelQueryTask timeoutTask = null;

                try {
                    timeoutTask = startQueryTimer(this, getTimeoutInMillis());

                    if (!isBatch) {
                        statementBegins();
                    }
                    //執(zhí)行sql并返回結(jié)果
                    rs = ((NativeSession) locallyScopedConnection.getSession()).execSQL(this, null, maxRowsToRetrieve, (NativePacketPayload) sendPacket,
                            createStreamingResultSet, getResultSetFactory(), metadata, isBatch);

                    if (timeoutTask != null) {
                        stopQueryTimer(timeoutTask, true, true);
                        timeoutTask = null;
                    }

                } finally {
                    if (!isBatch) {
                        this.query.getStatementExecuting().set(false);
                    }

                    stopQueryTimer(timeoutTask, false, false);
                }

                return rs;
            } catch (NullPointerException npe) {
                checkClosed(); // we can't synchronize ourselves against async connection-close due to deadlock issues, so this is the next best thing for
                              // this particular corner case.

                throw npe;
            }
        }
    }
}

參考:
https://www.cnblogs.com/java-chen-hao/p/11758412.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容