1.四种解析mapper方式 : package,resource,url,class。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//package引入
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//resource引入
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
//url引入
ErrorContext.instance().resource(url);
try (InputStream inputStream = Resources.getUrlAsStream(url)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
//mapperClass引入
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
2.跟一下package引入。
public void addMapper(Class 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<>(type));
//解析Mapper接口
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
String resource = type.toString();
//判断该resource有没有引入
if (!configuration.isResourceLoaded(resource)) {
//加载该命名空间下对应的xml
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//解析二级缓存注解CacheNamespace
parseCache();
//解析二级缓存注解CacheNamespaceRef
parseCacheRef();
for (Method method : type.getMethods()) {
//检查下method类型,不能是桥接方法和接口中的默认方法
if (!canHaveStatement(method)) {
continue;
}
//select操作解析@select,@SelectProvider注释方法中的带有@ResultMap的方法
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
//解析Statement
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
//解析IncompleteMethod,解析失败的方法。
parsePendingMethods();
}
void parseStatement(Method method) {
//取得方法的参数类型
final Class<?> parameterTypeClass = getParameterType(method);
//根据@Lang取得方法的LanguageDriver,默认是XMLLanguageDriver
final LanguageDriver languageDriver = getLanguageDriver(method);
//解析所有合法的statement注解,譬如@Select, @Update, @Insert, @Delete, @SelectProvider...
getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
//生成SqlSource
final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
//取得@Options注解
final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options) x.getAnnotation()).orElse(null);
//设置mappedStatementId,方法的全路径
final String mappedStatementId = type.getName() + "." + method.getName();
final KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
//插入和更新需要涉及到主键自动生成
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// @SelectKey注解生成主键
SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class).map(x -> (SelectKey) x.getAnnotation()).orElse(null);
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 {
//根据@Options中的value值实现自定义设置
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = configuration.getDefaultResultSetType();
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
if (options != null) {
//设置对应的options
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();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}
String resultMapId = null;
if (isSelect) {
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
//根据 @ResultMap({ "xx", "xxx" })设置resultMapId
resultMapId = String.join(",", resultMapAnnotation.value());
} else {
//没配置@ResultMap则走generateResultMapName方法生成resultMapId(方法的详细路径+“-”+参数类型)
resultMapId = generateResultMapName(method);
}
}
//生成Statement`
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,
statementAnnotation.getDatabaseId(),
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
});
}
3.其它的引入方式resource和url方式,就是先解析一波xml文件,大同小异。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//解析mapper.xml文件
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//解析对应空间下的mapper接口(跟上面的package方法一样的解析过程)
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置命名空间
builderAssistant.setCurrentNamespace(namespace);
//设置二级缓存
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析parameterMap
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql片段
sqlElement(context.evalNodes("/mapper/sql"));
//解析真正的sql语句
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);
}
}
mapper的解析分注解如@Select和xml两种解析,看代码我们可以看出来,同一个方法都是先找xml,后分析注解,根据configuration.isResourceLoaded
来判断是否已经解析,防止重复解析。