數(shù)據(jù)讀寫的本質(zhì)
不管是哪種ORM框架,數(shù)據(jù)讀寫其本質(zhì)都是對(duì)JDBC的封裝,其目的主要都是簡(jiǎn)化JDBC的開發(fā)流程喝滞,進(jìn)而讓開發(fā)人員更關(guān)注業(yè)務(wù)。下面是JDBC的核心流程:
- 注冊(cè) JDBC 驅(qū)動(dòng)(Class.forName("XXX");)
- 打開連接(DriverManager.getConnection("url","name","password"))
- 根據(jù)連接尤勋,創(chuàng)建 Statement(conn.prepareStatement(sql))
- 設(shè)置參數(shù)(stmt.setString(1, "wyf");)
- 執(zhí)行查詢(stmt.executeQuery();)
- 處理結(jié)果,結(jié)果集映射(resultSet.next())
- 關(guān)閉資源(finally)
Mybatis也是在對(duì)JDBC進(jìn)行封裝茵宪,它將注冊(cè)驅(qū)動(dòng)和打開連接交給了數(shù)據(jù)庫連接池來負(fù)責(zé)最冰,Mybatis支持第三方數(shù)據(jù)庫連接池,也可以使用自帶的數(shù)據(jù)庫連接池稀火;創(chuàng)建 Statement和執(zhí)行查詢交給了StatementHandler
來負(fù)責(zé)锌奴;設(shè)置參數(shù)交給ParameterHandler
來負(fù)責(zé);最后兩步交給了ResultSetHandler
來負(fù)責(zé)憾股。
Executor內(nèi)部運(yùn)作過程
測(cè)試方法
org.apache.ibatis.binding.BindingTest#shouldFindThreeSpecificPosts
下面是一個(gè)簡(jiǎn)單的數(shù)據(jù)讀寫過程,我們?cè)诤暧^上先來了解一下每一個(gè)組件在整個(gè)數(shù)據(jù)讀寫上的作用:
Mybatis的數(shù)據(jù)讀寫主要是通Excuter來協(xié)調(diào)StatementHandler箕慧、ParameterHandler和ResultSetHandler三個(gè)組件來實(shí)現(xiàn)的:
- StatementHandler:它的作用是使用數(shù)據(jù)庫的Statement或PrepareStatement執(zhí)行操作服球,啟承上啟下作用;
- ParameterHandler:對(duì)預(yù)編譯的SQL語句進(jìn)行參數(shù)設(shè)置颠焦,SQL語句中的的占位符“斩熊?”都對(duì)應(yīng)BoundSql.parameterMappings集合中的一個(gè)元素,在該對(duì)象中記錄了對(duì)應(yīng)的參數(shù)名稱以及該參數(shù)的相關(guān)屬性
- ResultSetHandler:對(duì)數(shù)據(jù)庫返回的結(jié)果集(ResultSet)進(jìn)行封裝伐庭,返回用戶指定的實(shí)體類型粉渠;
StatementHandler
StatementHandler類圖
RoutingStatementHandler
通過StatementType
來創(chuàng)建StatementHandler
,使用靜態(tài)代理模式來完成方法的調(diào)用圾另,主要起到路由作用霸株。它是Excutor組件真正實(shí)例化的組件。
public class RoutingStatementHandler implements StatementHandler {
/**
* 靜態(tài)代理模式
*/
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根據(jù){@link org.apache.ibatis.mapping.StatementType} 來創(chuàng)建不同的實(shí)現(xiàn)類 (策略模式)
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());
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
...
}
BaseStatementHandler
所有子類的抽象父類集乔,定義了初始化statement的操作順序去件,由子類實(shí)現(xiàn)具體的實(shí)例化不同的statement(模板模式)。
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 實(shí)例化Statement(由子類實(shí)現(xiàn))【模板方法+策略模式】
statement = instantiateStatement(connection);
// 設(shè)置超時(shí)時(shí)間
setStatementTimeout(statement, transactionTimeout);
// 設(shè)置獲取數(shù)據(jù)記錄條數(shù)
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;
instantiateStatement()
就是一個(gè)模板方法,由子類實(shí)現(xiàn)尤溜。
SimpleStatementHandler
使用JDBCStatement
執(zhí)行模式倔叼,不需要做參數(shù)處理,源碼如下:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 實(shí)例化Statement
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.createStatement();
} else {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) {
// N/A
// 使用Statement是直接執(zhí)行sql 所以沒有參數(shù)
}
PreparedStatementHandler
使用JDBCPreparedStatement
預(yù)編譯執(zhí)行模式宫莱。
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 實(shí)例化PreparedStatement
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
// 參數(shù)處理
parameterHandler.setParameters((PreparedStatement) statement);
}
CallableStatementHandler
使用JDBCCallableStatement
執(zhí)行模式丈攒,用來調(diào)用存儲(chǔ)過程。現(xiàn)在很少用授霸。
ParameterHandler
主要作用是給PreparedStatement
設(shè)置參數(shù)巡验,源碼如下:
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 獲取參數(shù)映射關(guān)系
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 循環(huán)獲取處理參數(shù)
for (int i = 0; i < parameterMappings.size(); i++) {
// 獲取對(duì)應(yīng)索引位的參數(shù)
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 獲取參數(shù)名稱
String propertyName = parameterMapping.getProperty();
// 判斷是否是附加參數(shù)
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
}
// 判斷是否是沒有參數(shù)
else if (parameterObject == null) {
value = null;
}
// 判斷參數(shù)是否有相應(yīng)的 TypeHandler
else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
// 以上都不是,通過反射獲取value值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 獲取參數(shù)的類型處理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 根據(jù)TypeHandler設(shè)置參數(shù)
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
- 獲取參數(shù)映射關(guān)系
- 獲取參數(shù)名稱
- 根據(jù)參數(shù)名稱獲取參數(shù)值
- 獲取參數(shù)的類型處理器
- 根據(jù)TypeHandler設(shè)置參數(shù)值
通過上面的流程可以發(fā)現(xiàn)绝葡,真正設(shè)置參數(shù)是由TypeHandler來實(shí)現(xiàn)的深碱。
TypeHandler
Mybatis基本上提供了我們所需要用到的所有TypeHandler,當(dāng)然我們也可以自己實(shí)現(xiàn)藏畅。TypeHandler的主要作用是:
- 設(shè)置PreparedStatement參數(shù)值
- 獲取查詢結(jié)果值
public interface TypeHandler<T> {
/**
* 給{@link PreparedStatement}設(shè)置參數(shù)值
*
* @param ps {@link PreparedStatement}
* @param i 參數(shù)的索引位
* @param parameter 參數(shù)值
* @param jdbcType 參數(shù)類型
* @throws SQLException
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 根據(jù)列名獲取結(jié)果值
*
* @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
/**
* 根據(jù)索引位獲取結(jié)果值
*/
T getResult(ResultSet rs, int columnIndex) throws SQLException;
/**
* 獲取存儲(chǔ)過程結(jié)果值
*/
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
TypeHandler的本質(zhì)就是對(duì)JDBC中
stmt.setString(1, "wyf");
和resultSet.getString("name")
的封裝敷硅,JDBC完整代碼可以查看JDBC 面試要點(diǎn)。
ResultSetHandler
ResultSetHandler
主要作用是:對(duì)數(shù)據(jù)庫返回的結(jié)果集(ResultSet)進(jìn)行封裝愉阎,通過通過ResultMap
配置和反射完成自動(dòng)映射绞蹦,返回用戶指定的實(shí)體類型;核心思路如下:
- 根據(jù)
RowBounds
做分頁處理 - 根據(jù)
ResultMap
配置的返回值類型和constructor
配置信息實(shí)例化目標(biāo)類 - 根據(jù)
ResultMap
配置的映射關(guān)系榜旦,獲取到TypeHandler
幽七,進(jìn)而從ResultSet
中獲取值 - 根據(jù)
ResultMap
配置的映射關(guān)系,獲取到目標(biāo)類的屬性名稱溅呢,然后通過反射給目標(biāo)類賦值
源碼太多了澡屡,這里就不發(fā)了,有興趣就在下面的源碼上看注釋吧咐旧,下面的流程圖會(huì)畫出方法的調(diào)用棧驶鹉。
Mybatis自帶的
RowBounds
分頁是邏輯分頁,數(shù)據(jù)量大了有可能會(huì)內(nèi)存溢出铣墨,所以不建議使用Mybatis默認(rèn)分頁室埋。
數(shù)據(jù)讀取流程圖
總結(jié)
Mybatis的整個(gè)數(shù)據(jù)讀取流程其實(shí)就是對(duì)JDBC的一個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn)。