目录
- 1.前言
- 2. `XML` 文件和 `Mapper` 接口的解析
-
- 2.1.`XML` 文件的解析
- 2.2. `Mapper` 接口的解析
- 3. `XML` 文件信息和 `Mapper` 接口的使用
-
- 3.1. `Mapper` 接口的使用
- 3.2. `XML` 文件信息的使用
- 4. 总结
1.前言
使用过 MyBatis
的,都知道它有 Mapper
接口和 Mapper.xml
文件,那么它们是如何关联起来的呢?,又是如何一起运作的呢?接下来,我们从源码级别来分析回答这个问题
2. XML
文件和 Mapper
接口的解析
2.1.XML
文件的解析
XMl
文件的解析过程太过复杂,在这里不再过多赘述,详细过程 在这里,我们先从 这里的 2.5 节点谈起,代码如下
public void parseStatementNode() {
// 省略所有的属性解析......
// 将解析出来的所有参数添加到 mappedStatements 缓存
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
// MapperBuilderAssistant.java
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 1.将 id 填充上 namespace,id = namespace + id(方法名)
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 2.使用参数构建 MappedStatement.Builder
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 3.使用 MappedStatement.Builder 构建 MappedStatement
MappedStatement statement = statementBuilder.build();
// 4.将 MappedStatement 添加到缓存
configuration.addMappedStatement(statement);
return statement;
}
statement
:表示解析出来的Mapper.xml
文件的所有参数信息- 上述方法做了两件事,构建
statement
,然后将其添加进mappedStatements
缓存中 mappedStatements
数据结构是一个StrictMap
,此处就是Mapper
接口方法为什么不能重载的答案,参见文章
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
2.2. Mapper
接口的解析
我们先从 这里的 2.6 节点谈起,代码如下
private void bindMapperForNamespace() {
// 拿到当前的 namespace,<mapper namespace="org.example.dao.UserMapper">
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 1.解析 namespace 对应的绑定类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {}
if (boundType != null && !configuration.hasMapper(boundType)) {
// 2.boundType 不为空,并且 configuration 还没有添加 boundType,
// 则将 namespace 添加到已加载列表,将 boundType 添加到 knownMappers 缓存
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);// addMapper() 方法调用如下
}
}
}
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);// addMapper() 方法调用如下
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 将 namespace 对应的绑定类型放到 knownMappers 缓存中
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
- 先看看
knownMappers
是个什么玩意,原来是一个HashMap
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
- 将解析过的
mapper.xml
文件的namespace
的绑定类型type
放到knownMappers
缓存中 - 这里的
type
可以理解为Mapper
接口的类型,即Mapper
接口
3. XML
文件信息和 Mapper
接口的使用
通过上面的分析,此时 MyBatis
已经完成了对 Mapper
接口和 XML
文件的解析工作,并且将它们分别添加进了各自的 Map
缓存中,那么它们是在哪里又取出来使用的呢?
3.1. Mapper
接口的使用
在 MapperRegistry
类中的 getMapper()
方法
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取已经解析完成的 Mapper 接口信息
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 实例化对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
knownMappers.get()
:获取namespace
的绑定类型,即Mapper
接口类型信息- 在上述的
2.2.
节点中的addMapper()
方法中有knownMappers.put()
- 在此时拿到
Mapper
接口类型很关键,因为马上就要创建Mapper
接口的代理对象了,代理对象创建过程 在这里的 2.1.4.节点
3.2. XML
文件信息的使用
DefaultSqlSession
类中增删改查如下,具体代码的跟进过程 看这里 2.2.节点
@Override
public int insert(String statement, Object parameter) {
// 调用下面的 update() 方法
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
// 从 mappedStatements 缓存拿到对应的 MappedStatement 对象,执行更新操作
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public int delete(String statement, Object parameter) {
// 调用上面的 update() 方法
return update(statement, parameter);
}
// select,以 executeForMany 为例
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 1.参数转换成sql命令参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// selectList() 方法在最下面
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
// 2.执行查询操作,selectList() 方法在最下面
result = sqlSession.selectList(command.getName(), param);
}
// 3.处理返回结果
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// selectList() 方法在最下面
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从 mappedStatements 缓存中拿到对应的 MappedStatement 对象,执行查询操作
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
关键部分代码,在即将要执行 SQL
时,在从 StrictMap
的缓存中获取
// 参数 statement 是 String 类型的
MappedStatement ms = configuration.getMappedStatement(statement);
// Configuration 类中的 getMappedStatement() 方法如下
public MappedStatement getMappedStatement(String id) {
// 调用下面的方法
return this.getMappedStatement(id, true);
}
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
//
return mappedStatements.get(id);
}
// mappedStatements 的数据类型
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
4. 总结
Mapper
接口是怎么和 XML
文件关联起来,最关键的部分
// 拿到当前的 namespace,<mapper namespace="org.example.dao.UserMapper">
// MyBatis 会解析为 "org.example.dao.UserMapper"
String namespace = builderAssistant.getCurrentNamespace();
// 解析为 namespace 对应的绑定类型
boundType = Resources.classForName(namespace);
相关文章
暂无评论...