上一篇我们了解到了MappedStatement类就是mapper.xml中的一个sql语句,而Configuration初始化的时候会加载所有的mapper接口类,而本篇再分析下是如何将mapper接口和xml进行绑定的。
先从上一篇的源码开始分析:
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 {
knownMappers.put(type, new MapperProxyFactory<T>(type));//加载指定的mapper接口
//9
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);//?
//10
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
如果猜的没错的话,那么第9行和第10行就是解析xml并初始化MappedStatement对象的代码了。那么就先看看MapperAnnotationBuilder(mapper注解构造类)
MapperAnnotationBuilder类的构造方法如下:
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
MapperAnnotationBuilder类的主要属性有:
private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();//sql语句上的注解
private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();//指定类指定方法上的sql语句注解
private Configuration configuration;//全局配置对象
private MapperBuilderAssistant assistant;//Mapper构建助手类,组装解析出来的配置,生成Cache、ResultMap以及MappedStatement对象
private Class<?> type;//解析的目标mapper接口的Class对象
MapperAnnotationBuilder构造方法就是给自己的属性进行了初始化,其中两个Set集合在初始化时表明了支持 @Select、@Insert、@Update、@Delete以及对应的Provider注解,本篇不再介绍,和xml配置的写法只是写法上不一样而已,实际的作用是一样,本文只分析xml这种用法。
由第一段代码可知,MapperAnnotationBuilder有一个parse方法,也是创建MappedStatement的方法,源码如下:
public void parse() {
String resource = type.toString();//mapper接口名,如:interface com.lucky.test.mapper.UserMapper
//Configuration中Set<String> loadedResources 保存着已经加载过的mapper信息
if (!configuration.isResourceLoaded(resource)) {//判断是否已经加载过
loadXmlResource();//加载XML资源
configuration.addLoadedResource(resource);//加载的resource添加到Configuration的loadedResources中
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();//遍历mapper的所有方法
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
可见xml加载是通过调用loadXmlResource方法,源码如下:
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";//通过mapper接口名找到对应的mapper.xml路径
InputStream inputStream = null;
try {
//读取mapper.xml文件流
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
//根据mapper.xml文件流创建XMLMapperBuilder对象
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//执行parse方法
xmlParser.parse();
}
}
}
这里又涉及到了XMLMapperBuilder类,很明显是mapper.xml构建者类,构造方法源码如下:
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
this(inputStream, configuration, resource, sqlFragments);//调用下面一个构造方法
this.builderAssistant.setCurrentNamespace(namespace);
}
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);//调用下面一个构造方法
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
//对自己的属性进行赋值
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);//mapper构造者助手
this.parser = parser;//XML解析类
this.sqlFragments = sqlFragments;//sql片段集合
this.resource = resource;//mapper名称
}
那么再看下parse方法,源码如下:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));//从XML解析类中获取mapper标签的内容
configuration.addLoadedResource(resource);//将加载过的resource添加到Configuration中
bindMapperForNamespace();//绑定mapper到对应mapper的命名空间中
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
而解析xml的主要方法肯定就是configurationElement方法了,源码如下:
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");//从xml中获取namespace标签内容
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));//获取cache-ref标签内容
cacheElement(context.evalNode("cache"));//获取cache标签内容
parameterMapElement(context.evalNodes("/mapper/parameterMap"));//获取parameterMap内容
resultMapElements(context.evalNodes("/mapper/resultMap"));//获取resultMap内容
sqlElement(context.evalNodes("/mapper/sql"));//获取sql标签内容
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析获取各种sql语句标签内容
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
很显然这个方法就是将xml中所有可能存在的标签都进行了解析处理,本文就不再详细分析,留得青山在不愁没柴烧。既然知道了xml的各个标签是在哪里解析的,就不愁不知道具体是怎么解析的了。分析到这里,似乎还是没有看到MappedStatement的身影,别急,让我们回到MapperAnnotationBuilder
的parse方法,先是通过loadXmlResource方法进行xml文件的解析加载,然后添加已经加载过的resource到Configuration中,然后解析二级缓存的配置,然后就是获取mapper接口的所有方法,遍历解析:
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
parseStatement(method)是获取方法的属性,然后将xml中配置的标签进行一一匹配,源码如下:
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
} else {
keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = new NoKeyGenerator();
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
最终是调用MapperBuilderAssistant对象的addMappedStatement方法创建了MappedStatement对象,源码如下:
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");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
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);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
创建了MappedStatement对象之后,就调用了Configuration的addMappedStatement方法,从而添加到了Configuration中的Map<String, MappedStatement> mappedStatements集合中,而key就是MappedStatement的id
而到目前为止,MappedStatement对象虽然被创建了,也已经加入到了Configuration管理的集合中去了,可是还没有和mapper接口建立上直接的关系。那么这两个又是如何最终联系到一起去的呢?下一篇再慢慢分析。