正文
我們上篇文章講到了查詢方法里面的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;
}
}
}
}