建行二面:Mybatis 是如何工作的?
MyBatis 是一款優秀的持久層框架,它通過簡化 JDBC操作和提供靈活的 SQL映射方式,使 Java 開發人員能夠更高效地進行數據庫操作。那么,MyBatis的執行原理是什么?這篇文章我們將深入地分析。
一、MyBatis 配置解析
MyBatis 的配置文件通常包括全局配置文件(mybatis-config.xml)和映射文件(XXXMapper.xml)。全局配置文件主要用于配置數據源和其他全局性的信息,而映射文件則用于定義 SQL 語句。
1. 全局配置文件解析
全局配置文件在 MyBatis 啟動時被解析。SqlSessionFactoryBuilder 是 MyBatis 解析配置文件的入口點。它通過 build 方法接收一個 Reader 或 InputStream,然后調用 XMLConfigBuilder 來解析 XML 配置文件。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
XMLConfigBuilder 解析配置文件并構建出 Configuration 對象,該對象包含了 MyBatis 的所有配置信息。
2. 映射文件解析
映射文件中定義了 SQL 語句,通過 XMLMapperBuilder 進行解析。每個 <mapper> 標簽對應一個 MappedStatement 對象,MappedStatement 包含了 SQL 語句、輸入輸出參數類型、結果集映射等信息。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
二、SQL 語句解析
MyBatis 支持動態 SQL,通過 <if>, <choose>, <foreach> 等標簽,可以根據不同的條件構造 SQL。動態 SQL 是 MyBatis 的一大特色,通過 SqlSource 接口實現。SqlSource 的主要實現類有 StaticSqlSource, DynamicSqlSource, RawSqlSource 等。
動態 SQL 解析
DynamicSqlSource 是處理動態 SQL 的核心類。它通過 SqlNode 樹來表示 SQL 語句的結構,SqlNode 是一個接口,常用的實現類有 IfSqlNode, ChooseSqlNode, WhereSqlNode 等。每個 SqlNode 的 apply 方法負責將節點轉換為 SQL 字符串。
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
return sqlSource.getBoundSql(parameterObject);
}
}
三、參數設置
在獲得最終的 SQL 語句后,MyBatis 需要將參數傳遞給 SQL 語句。ParameterHandler 接口負責這項工作,默認實現是 DefaultParameterHandler。
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
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);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
}
四、SQL 執行
SQL 執行是 MyBatis 的核心功能之一。Executor 接口定義了執行操作的基本方法,主要的實現類有 SimpleExecutor, ReuseExecutor, BatchExecutor。這些執行器通過 StatementHandler 執行 SQL 語句。
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
}
五、結果處理
MyBatis 提供了強大的結果集映射功能,允許將 SQL 查詢結果映射為 Java 對象。ResultSetHandler 接口負責處理結果集,DefaultResultSetHandler 是其主要實現類。
public class DefaultResultSetHandler implements ResultSetHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final ObjectFactory objectFactory;
private final boolean useConstructorMappings;
private final ReflectorFactory reflectorFactory;
private final MappedStatement mappedStatement;
private final RowBounds rowBounds;
private final ParameterHandler parameterHandler;
private final ResultHandler<?> resultHandler;
private final BoundSql boundSql;
public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.objectFactory = mappedStatement.getConfiguration().getObjectFactory();
this.useConstructorMappings = mappedStatement.getConfiguration().isUseConstructorMappings();
this.reflectorFactory = mappedStatement.getConfiguration().getReflectorFactory();
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.parameterHandler = parameterHandler;
this.resultHandler = resultHandler;
this.boundSql = boundSql;
}
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
return collapseSingleResultList(multipleResults);
}
}
六、總結
本文,我們通過核心源碼分析了 MyBatis, 它是一個輕量級的 ORM框架,它通過配置文件和注解將 Java 對象與數據庫記錄映射起來,其核心在于通過 XML和注解配置 SQL語句,利用執行器執行 SQL,并通過結果集處理器將結果映射為 Java對象。
MyBatis的設計使得開發者可以專注于 SQL本身,而不必關心底層 JDBC操作的細節,了解和掌握其執行原理和設計模式,可以幫組我們在實際應用中更好地使用 MyBatis。