1、概述
解析mapper,mybatis中提供了四种方式来注册mapper。
(1)、采用package的方式,这种方式因为class已经提供了,直接使用Configuration的添加mapper方式来注册。
(2)、采用resource方式,这种方式一般是用户提供一个mapper.xml的配置文件。然后使用XMLMapperBuilder来解析。
(3)、采用url方式,这种方式与resources的方式一致。
(4)、采用class的方式,这种方式与package是一致的。
特别注意,mybatis一般只会使用class和xml文件其中一种方式来注册。
XMLMapperBuilder是用来解析mapper.XML文件。XMLMapperBuilder解析完XML文件后还需要调用MapperAnnotationBuilder来处理incomplete的方法。
MapperAnnotationBuilder是用来解析Annotation mapper的。
2、XMLMapperBuilder的整体解析过程
解析mapper的过程一般分为两类:
一类是指定资源,比如resource或者url,这种一般是用户提供了xml文件,此时需要解析XML文件,然后注册mapper。
另一类是指定class类型,比如package或者class,这种一般是用户采用了注解的方式,此时需要解析class,然后注册mapper。
而mapper的解析的门面是MapperAnnotationBuilder这个类,该类两种方式都提供了解析,也可以直接调用XMLMapperBuilder的parse解析。
XMLMapperBuilder的类字段如下:
// 使用XPathParser解析XML文件
private final XPathParser parser;
// 持有MapperBuilder的引用,主要的作用是用来注册解析的mapper
private final MapperBuilderAssistant builderAssistant;
// 缓存解析的XNode
private final Map<String, XNode> sqlFragments;
// 资源名
private final String resource;
先来说明下XMLMapperBuilder中的元素是如何注入到Configuration中的。
XMLMapperBuilder类主要是用于解析mapper文件。
这里重点讲下MapperRegistry的注册mapper逻辑:
// 解析只提供包名的情况
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 获取包名下的全部类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 循环注册扫描到的class
addMapper(mapperClass);
}
}
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 {
// 注册mapper,这个mapper是通过JDK动态代理MapperProxyFactory来操作的。
knownMappers.put(type, new MapperProxyFactory<>(type));
// 解析Mapper接口中的注解信息,并注册到Configuration中
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
上面使用了MapperProxyFactory,这里创建生成动态代理工厂。后续会创建一个关于接口的动态代理类。
MapperAnnotationBuilder的parse方法解析代码如下:
public void parse() {
String resource = type.toString();
// 如果类型没有被解析过
if (!configuration.isResourceLoaded(resource)) {
// 解析对于类型名的xml文件
loadXmlResource();
// 缓存已经解析过的类型
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
// 解析存在注解的情况@Select|@Update|@Insert|@Delete
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getSqlCommandType(method) == SqlCommandType.SELECT && method.getAnnotation(ResultMap.class) == null) {
// 解析查询语句
parseResultMap(method);
}
try {
// 解析其他类型语句
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 解析还没有解析完成的方法
parsePendingMethods();
}
首先来看下解析mapper XML文件逻辑:
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";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
// 使用XMLMapperBuilder来解析Mapper.xml文件
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
解析Mapper.xml文件是使用XMLMapperBuilder类的parse来完成的。
先来看下XMLMapperBuilder这个类:
XMLMapperBuilder这个类是用来解析mapper.xml文件内容,并将文件内容转化成对应的数据结构的。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper.xml文件中mapper的节点内容
configurationElement(parser.evalNode("/mapper"));
// 缓存已经解析内容
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 解析构建ResultMap
parsePendingResultMaps();
// 解析构建cache
parsePendingCacheRefs();
// 解析构建select|insert|update|delete
parsePendingStatements();
}
3、解析mapper.xml文件中mapper的节点内容
解析mapper节点是通过configurationElement方法来完成的。代码如下:
private void configurationElement(XNode context) {
try {
// 首先获取mapper标签的namespace属性
String namespace = context.getStringAttribute("namespace");
// 如果namespace属性为空,则抛出BuilderException异常
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 在MapperBuilder中标记当前的namespace
builderAssistant.setCurrentNamespace(namespace);
// 解析cache-ref标签
cacheRefElement(context.evalNode("cache-ref"));
// 解析cache标签
cacheElement(context.evalNode("cache"));
// 解析parameterMap标签,被废弃了
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析resultMap标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析sql标签
sqlElement(context.evalNodes("/mapper/sql"));
// 解析select|insert|update|delete标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
3.1、解析cache-ref标签
这里所说的cache就是mybatis中的二级缓存,相较于一级缓存(SqlSession)二级缓存是一种针对于某个名称空间的缓存cache,而cache-ref标签主要是用来引用其他名称空间的缓存。
cache-ref原理如下:
代码如下:
private void cacheRefElement(XNode context) {
if (context != null) {
// 在Configuration中设置namespace属性
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
// 构建CacheRef解析器
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// 调用CacheRef解析器的resolveCacheRef方法来解析,这里第一次是解析不到的,会抛出IncompleteElementException异常
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
// 如果捕获了IncompleteElementException异常,就将这个cache放到还未解析完成的CacheRef中。
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
CacheRefResolver中的resolveCacheRef方法如下:
public Cache resolveCacheRef() {
// 调用的是MapperBuilderAssistant的useCacheRef方法。
return assistant.useCacheRef(cacheRefNamespace);
}
MapperBuilderAssistant的useCacheRef方法如下:
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
// 从Configuration中获取Cache
Cache cache = configuration.getCache(namespace);
// 如果此时获取不到,那么就抛出IncompleteElementException异常,等待所有Mapper的Cache标签被解析完成之后再获取。
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
// 同样的处理办法
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
这里有个疑问为什么不把cache-ref标签的解析放到cache的后面呢。其实如果放到cache标签后面也是一样的,因为cache-ref标签的namespace可以指向其他mapper,此时还是会有没有解析完cache的mapper。所以这里与顺序无关。
3.2、解析cache标签
cache标签是用来支持Mybatis的二级缓存功能的。
举个例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
cache标签是通过XMLConfigBuilder的cacheElement方法来实现的。
private void cacheElement(XNode context) {
if (context != null) {
// 首先获取cache标签的type属性,如果type属性不存在,默认是PERPETUAL,也就是使用PerpetualCache。
String type = context.getStringAttribute("type", "PERPETUAL");
// 获取Cache的类型
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 获取eviction属性,缺省情况下为LRU。
String eviction = context.getStringAttribute("eviction", "LRU");
// 获取eviction的类型
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 获取flushInterval属性
Long flushInterval = context.getLongAttribute("flushInterval");
// 获取size属性
Integer size = context.getIntAttribute("size");
// 获取readOnly属性,默认为false
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 获取blocking属性,默认为false
boolean blocking = context.getBooleanAttribute("blocking", false);
// 获取cache标签下面全部的子标签,并封装成properties
Properties props = context.getChildrenAsProperties();
// 创建Cache对象,并注册到Configuration中
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
builderAssistant的useNewCache方法如下:
// 通过构造器模式创建Cache
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
3.3、解析resultMap标签
resultMap是Mybatis将JDBC类型数据转换为Java Bean对象的映射关系类。
它的数据结构如下所示:
// 持有Configuration属性
private Configuration configuration;
// ResultMap标签的id
private String id;
// ResultMap标签的type属性
private Class<?> type;
// 记录除discriminator节点之外的其他映射关系
private List<ResultMapping> resultMappings;
// 记录映射关系中带有ID标志的映射关系
private List<ResultMapping> idResultMappings;
// 记录映射关系中带有constructor标志的映射关系
private List<ResultMapping> constructorResultMappings;
// 记录映射关系中不带有constructor的标志的映射关系
private List<ResultMapping> propertyResultMappings;
// 记录所有映射关系中涉及column属性集合
private Set<String> mappedColumns;
// 记录所有映射关系中property属性集合
private Set<String> mappedProperties;
// 鉴别器
private Discriminator discriminator;
// 是否存在resultMap嵌套的情况
private boolean hasNestedResultMaps;
// 是否存在嵌套查询的情况
private boolean hasNestedQueries;
// 是否自动映射,如果将属性设置为true,则启动自动映射功能
// 即自动查找与列名相同名称的属性,通过setter方法赋值,如果为false则需要手动注名映射关系才可以调用setter方法赋值。
private Boolean autoMapping;
ResultMapping对象字段如下:
// 持有Configuration对象
private Configuration configuration;
// resultMap子节点的property属性值
private String property;
// resultMap子节点的column属性值
private String column;
// resultMap子节点的javaType属性
private Class<?> javaType;
// resultMap子节点的jdbcType属性
private JdbcType jdbcType;
// resultMap子节点的typeHandler属性,类型转换器
private TypeHandler<?> typeHandler;
// join方式关联查询时会用到
private String nestedResultMapId;
// 子查询会用到
private String nestedQueryId;
// 对应节点的notNullColumns属性拆分后的结果
private Set<String> notNullColumns;
// resultMap子节点的columnPrefix属性
private String columnPrefix;
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;
private String foreignColumn;
// 是否延迟加载
private boolean lazy;
这两个类的结构如下所示:
解析resultMap标签:
private void resultMapElements(List<XNode> list) {
for (XNode resultMapNode : list) {
try {
// 调用resultMapElement方法解析mapper中的resultMap标签
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获取resultMap的type属性,如果type属性不存在,则取ofType属性,否则取resultType,如果不存在继续取javaType.
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 获取type的Class类型
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
// 获取resultMap的子节点
for (XNode resultChild : resultChildren) {
// 如果子节点的为constructor
if ("constructor".equals(resultChild.getName())) {
// 解析constructor节点
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
// 解析discriminator节点
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 否则解析其他的标签
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 解析包括result在内的其他名称的标签
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 解析resultMap的id属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 解析resultMap的extends属性
String extend = resultMapNode.getStringAttribute("extends");
// 解析resultMap的autoMapping属性
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
buildResultMappingFromContext方法如下所示:
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
// 解析比如result标签中的property的属性
property = context.getStringAttribute("property");
}
// 解析标签中的column属性
String column = context.getStringAttribute("column");
// 解析标签中的javaType属性
String javaType = context.getStringAttribute("javaType");
// 解析标签中jdbcType属性
String jdbcType = context.getStringAttribute("jdbcType");
// 解析标签中的select属性
String nestedSelect = context.getStringAttribute("select");
// 解析标签中嵌套的resultMap属性
String nestedResultMap = context.getStringAttribute("resultMap", () ->
processNestedResultMappings(context, Collections.emptyList(), resultType));
// 解析notNullColumn属性
String notNullColumn = context.getStringAttribute("notNullColumn");
// 解析columnPrefix属性
String columnPrefix = context.getStringAttribute("columnPrefix");
// 解析typeHandler属性
String typeHandler = context.getStringAttribute("typeHandler");
// 解析resultSet属性
String resultSet = context.getStringAttribute("resultSet");
// 解析foreignColumn属性
String foreignColumn = context.getStringAttribute("foreignColumn");
// 判断是否延迟加载
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 调用builderAssistant的buildResultMapping方法构建ResultMapping对象
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
resultMapResolver.resolve方法最终会调用addResultMap方法:
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
// resultMap的完整ID是namespace.id
id = applyCurrentNamespace(id, false);
// 父resultMap的ID
extend = applyCurrentNamespace(extend, true);
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
}
resultMappings.addAll(extendedResultMappings);
}
// 构建ResultMap
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
// 注册resultMap到Configuration中
configuration.addResultMap(resultMap);
return resultMap;
}
最终注册到Configuration中的ResultMap的数据结构如下:
3.4、解析sql标签
解析sql标签的代码如下所示:
private void sqlElement(List<XNode> list) {
// 调用sqlElement的重载方法
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
// 循环解析所有的SQL标签
for (XNode context : list) {
// 获取SQL标签中databaseId属性
String databaseId = context.getStringAttribute("databaseId");
// 获取SQL标签的ID属性
String id = context.getStringAttribute("id");
// 转换成namespace.id的格式
id = builderAssistant.applyCurrentNamespace(id, false);
// 检测SQL标签中的databaseId是否与configuration中的ID是否一致
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 记录到Configuration中的sqlFragements集合中
sqlFragments.put(id, context);
}
}
}
3.5、解析select|insert|update|delete标签
MappedStatement类的字段如下:
private String resource;
// 持有configuration对象
private Configuration configuration;
// 标签的ID
private String id;
private Integer fetchSize;
private Integer timeout;
// 标签的类型
private StatementType statementType;
private ResultSetType resultSetType;
// 标签中的SQL
private SqlSource sqlSource;
// 二级缓存
private Cache cache;
private ParameterMap parameterMap;
// resultMap
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
// SQL语句解析驱动
private LanguageDriver lang;
private String[] resultSets;
解析方法如下所示:
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 使用XMLStatementBuilder解析statement类型的标签
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder的parse方法如下:
public void parseStatementNode() {
// 获取id属性
String id = context.getStringAttribute("id");
// 获取databaseId属性
String databaseId = context.getStringAttribute("databaseId");
// 判断是否是当前databaseId
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 获取节点名select|update|insert|delete
String nodeName = context.getNode().getNodeName();
// 转换成SqlCommand的类型
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 是否是select节点
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 获取flushCache属性
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// 获取useCache属性,如果不存在,默认情况下是判断是否是select标签
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 获取resultOrdered属性
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 获取parameterType属性
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取lang属性
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 解析selectKey属性
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 获取statementType属性,默认情况下是StatementType.PREPARED
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// 获取fetchSize属性
Integer fetchSize = context.getIntAttribute("fetchSize");
// 获取timeout属性
Integer timeout = context.getIntAttribute("timeout");
// 获取parameterMap属性
String parameterMap = context.getStringAttribute("parameterMap");
// 获取resultType属性
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
// 获取resultMap属性
String resultMap = context.getStringAttribute("resultMap");
// 获取resultSetType属性
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
// 获取keyProperty属性
String keyProperty = context.getStringAttribute("keyProperty");
// 获取keyColumn属性
String keyColumn = context.getStringAttribute("keyColumn");
// 获取resultSets属性
String resultSets = context.getStringAttribute("resultSets");
// 构建MappedStatement对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
至此,mapper.xml文件的内容已经全部解析完成。
4、解析annotation类型mapper
解析annotation类型的mapper是通过MapperAnnotationBuilder类来完成的。
该类中的字段如下:
// 存储Mybatis中支持的SQL语句类型的注解
private static final Set<Class<? extends Annotation>> SQL_ANNOTATION_TYPES = new HashSet<>();
// 存储SQL提供类的注解
private static final Set<Class<? extends Annotation>> SQL_PROVIDER_ANNOTATION_TYPES = new HashSet<>();
// 持有Configuration引用
private final Configuration configuration;
// 持有Mapper中的元素构造器,实际的构造操作
private final MapperBuilderAssistant assistant;
// mapper类型
private final Class<?> type;
// 备注:*Provider注解是用来标注返回SQL语句的类的方法
static {
// 定义了Mybatis中的SQL语句类型
SQL_ANNOTATION_TYPES.add(Select.class);
SQL_ANNOTATION_TYPES.add(Insert.class);
SQL_ANNOTATION_TYPES.add(Update.class);
SQL_ANNOTATION_TYPES.add(Delete.class);
// 定义了Mybatis中SQL语句提供注解类型
SQL_PROVIDER_ANNOTATION_TYPES.add(SelectProvider.class);
SQL_PROVIDER_ANNOTATION_TYPES.add(InsertProvider.class);
SQL_PROVIDER_ANNOTATION_TYPES.add(UpdateProvider.class);
SQL_PROVIDER_ANNOTATION_TYPES.add(DeleteProvider.class);
}
parse方法如下:
public void parse() {
// 获取mapper类型的名称
String resource = type.toString();
// 判断这个mapper是否被解析过
if (!configuration.isResourceLoaded(resource)) {
// 尝试解析下这个mapper的xml文件,比如com.matrix.dao.UserMapper.xml
loadXmlResource();
// 标记解析完成
configuration.addLoadedResource(resource);
// 设置当前的命名空间com.matrix.dao.UserMapper
assistant.setCurrentNamespace(type.getName());
// 解析Cache
parseCache();
// 解析CacheRef
parseCacheRef();
// 遍历全部的方法
for (Method method : type.getMethods()) {
// 如果不是Statement方法,则跳过
if (!canHaveStatement(method)) {
continue;
}
// 如果Sql类型是select并且不存在ResultMap注解,该注解是用来引用mapper中的其他@Results
if (getSqlCommandType(method) == SqlCommandType.SELECT && method.getAnnotation(ResultMap.class) == null) {
// 解析ResultMap
parseResultMap(method);
}
try {
// 解析Statement
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
4.1、解析Cache
@CacheNamespace注解主要用于mybatis二级缓存,等同于
<setting name="cacheEnabled" value="true"/>
parserCache方法如下:
private void parseCache() {
// 解析Mapper接口类型的CacheNamespace注解
CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
if (cacheDomain != null) {
// 获取CacheNamespace注解的size属性
Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
// 获取CacheNamespace注解的flushInterval属性
Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
// 获取CacheNamespace注解的properties
Properties props = convertToProperties(cacheDomain.properties());
// 调用MapperBuilderAssistant创建Cache
assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
}
}
4.2、解析CacheRef
解析Mapper接口中的CacheNamespaceRef注解
private void parseCacheRef() {
// 获取Mapper接口中的CacheNamespaceRef注解
CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
if (cacheDomainRef != null) {
// 获取CacheNamespaceRef注解中的value属性
Class<?> refType = cacheDomainRef.value();
// 获取CacheNamespaceRef注解中的name属性
String refName = cacheDomainRef.name();
// 如果value属性为void且name属性为空,则抛出BuilderException异常
if (refType == void.class && refName.isEmpty()) {
throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
}
// 如果refType和refName属性都不存在,也抛出BuilderException异常
if (refType != void.class && !refName.isEmpty()) {
throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
}
// 获取名称空间属性
String namespace = (refType != void.class) ? refType.getName() : refName;
try {
// 调用MapperBuilderAssistant指定名称空间
assistant.useCacheRef(namespace);
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
}
}
}
4.3、解析statement
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() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
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) {
// 解析resultMap
resultMapId = parseResultMap(method);
}
// 构建MappedStatement,并注册到Configuration中。
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);
}
}
创建MappedStatement对象并注册到Configuration中。
至此整个mapper的解析就完成了。