前言
難得復工了,公司百業待興,有一大堆接口需要對接,忙的不行。回過神來發現自己快一個月沒寫博客了,趕緊抽時間寫一寫,不能斷更。
截止上一篇博客,我們已經把結果集映射的內容介紹完畢,接下來就是對Mybatis中的核心接口進行介紹,通過介紹這些核心接口,使讀者們更深刻地理解Mybatis的運行機制以及原理。
StatementHandler
StatementHandler接口是Mybatis的核心接口之一,它完成了Mybatis中最核心的工作,也是Executor接口實現的基礎。
StatementHandler接口中功能有很多,如創建Statement對象、執行SQL語句、批量執行SQL語句等。StatementHandler接口定義如下。
/**
* Mybatis核心接口之一,完成了Mybatis中最核心的工作,也是Executor接口實現的基礎。
*
* @author Clinton Begin
*/
public interface StatementHandler {
/**
* 從數據庫連接中獲取一個Statement
*
* @param connection
* @param transactionTimeout
* @return
* @throws SQLException
*/
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
/**
* 綁定statement執行時所需的實參
*
* @param statement
* @throws SQLException
*/
void parameterize(Statement statement)
throws SQLException;
/**
* 批量執行sql語句
*
* @param statement
* @throws SQLException
*/
void batch(Statement statement)
throws SQLException;
/**
* 執行 update/insert/delete語句
*
* @param statement
* @return
* @throws SQLException
*/
int update(Statement statement)
throws SQLException;
/**
* 執行select語句
*
* @param statement
* @param resultHandler
* @param <E>
* @return
* @throws SQLException
*/
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
/**
* 查詢遊標
*
* @param statement
* @param <E>
* @return
* @throws SQLException
*/
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
/**
* 獲取BoundSql
*
* @return
*/
BoundSql getBoundSql();
/**
* 獲取ParameterHandler
*
* @return
*/
ParameterHandler getParameterHandler();
}
該接口的繼承關係如下。其中,CallableStatementHandler用於調用存儲過程,而mysql的存儲過程在大多數公司都很少使用甚至禁止使用,這裏就不對其進行介紹了,有興趣的朋友可以自己參考源碼閱讀
RoutingStatementHandler
RoutingStatementHandler使用策略模式,根據MappedStatement中指定的statementType字段,創建對應的StatementHandler接口實現。由於其這種思路,還有人認爲這是個路由。RoutingStatementHandler核心代碼如下。
public class RoutingStatementHandler implements StatementHandler {
/**
* 底層封裝的真正的StatementHandler對象
* 這裏的delegate在寫框架的過程中使用較多
*/
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// RoutingStatementHandler的主要功能是根據MappedStatement的配置,生成對應的StatementHandler對象
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
}
BaseStatementHandler
BaseStatementHandler是一個抽象類,實現了StatementHandler接口。它只提供了一些參數綁定相關的方法,對數據庫不進行任何操作。該類的字段如下。
protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
/**
* 記錄結果集映射對象
*/
protected final ResultSetHandler resultSetHandler;
/**
* 記錄使用的參數處理器對象。ParameterHandler的主要功能是爲SQL綁定實參
* 也就是使用傳入的實參替換SQL語句中的 ? 佔位符
*/
protected final ParameterHandler parameterHandler;
protected final Executor executor;
/**
* 記錄SQL語句對應的MappedStatement
*/
protected final MappedStatement mappedStatement;
/**
* 記錄用戶設置的offset和limit,用於在結果集中定位映射的起始位置和結束位置
*/
protected final RowBounds rowBounds;
protected BoundSql boundSql;
在BaseStatementHandler的構造方法中,除對上面的字段進行初始化之外,還會調用KeyGenerator.processBefore()方法初始化SQL的主鍵,具體實現如下。
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) {
// 調用KeyGenerator.processBefore 方法獲取主鍵
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
}
BaseStatementHandler實現了StatementHandler的prepare方法,該方法用來初始化Statement對象,然後爲其分配超時時間等屬性。其中,初始化StatementHandler的方法是個抽象方法instantiateStatement,是一個抽象方法,需要由子類去實現,代碼如下。
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 初始化statement對象,交給子類去實現
statement = instantiateStatement(connection);
// 設置超時時間
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);
}
}
BaseStatementHandler依賴兩個重要的組件,分別是ParameterHandler和ResultSetHandler。後者在前面的文章中已經介紹,不在重複。
ParameterHandler
通過前面介紹動態SQL可知,在BoundSql中記錄的SQL語句可能包含?佔位符,每個?佔位符都對應了BoundSql.parameterMapings集合中的一個元素。在ParameterHandler中只定義了一個setParameter方法,該方法用於爲SQL語句綁定實參。ParameterHandler接口只有唯一一個實現類 DefaultParameterHandler,核心字段如下。
/**
* 管理Mybatis中的全部TypeHandler對象
*/
private final TypeHandlerRegistry typeHandlerRegistry;
/**
* SQL節點
*/
private final MappedStatement mappedStatement;
/**
* 用戶傳入的實參對象
*/
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
DefaultParameterHandler的setParameters方法中會遍歷parameterMappings集合中記錄的ParameterMapping對象,並根據其中記錄的參數名稱找到對應的實參,再與SQL綁定。setParameters方法如下。
/**
* 設置參數
* @param ps
*/
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 取出參數映射列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 遍歷參數
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// OUT是存儲過程中的輸出參數,這裏需要過濾掉這些參數
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 獲取參數名稱
String propertyName = parameterMapping.getProperty();
// 獲取對應的實參值
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 實參可以直接通過TypeHandler轉換成jdbcType
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 獲取parameterMapping中設置的TypeHandler對象
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 爲語句綁定實參
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
對SQL語句綁定完實參後,就可以調用Statement對象的execute方法執行SQL了。
SimpleStatementHandler
SimpleStatementHandler是BaseStatementHandler的子類,底層使用了Statement對象完成數據庫的相關操作,所以SQL語句中不能存在佔位符,因此parameterize方法是空實現。
SimpleStatementHandler的instantiateStatement方法直接通過JDBC Connection創建Statement,代碼如下。
/**
* 創建statement對象
* @param connection
* @return
* @throws SQLException
*/
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.createStatement();
} else {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
query方法完成了數據庫查詢的操作,並通過ResultSetHandler將結果集映射成結果對象,代碼如下。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleResultSets(statement);
}
update方法負責執行insert、update、delete的SQL語句,並根據配置的KeyGenerator獲取數據庫生成的主鍵,具體實現如下。
/**
* 負責執行insert、update、delete語句
* @param statement
* @return
* @throws SQLException
*/
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
PreparedStatementHandler
該類底層依賴於PrepareStatement對象完成數據庫的操作,instantiateStatement方法直接調用Connection的prepareStatement方法創建PrepareStatement對象,代碼如下。
/**
* 直接調用Connection的prepareStatement方法創建PrepareStatement對象
* @param connection
* @return
* @throws SQLException
*/
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 獲取待執行的sql
String sql = boundSql.getSql();
// 根據keyGenerator的值創建PrepareStatement對象
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
// 返回數據庫生成的主鍵
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
// 在insert語句執行完成之後,將keyColumnNames指定的列返回
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// 設置結果集是否可以滾動以及遊標是否可以上下移動,設置結果集是否可更新
return connection.prepareStatement(sql);
} else {
// 創建普通的PrepareStatement對象
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
PrepareStatement的其他方法的實現與SimpleStatementHandler對應的方法實現類型,這裏就不贅述了。