首页 > 技术文章 > mybatis源码解析7---MappedStatement初始化过程

jackion5 2019-03-21 23:02 原文

上一篇我们了解到了MappedStatement类就是mapper.xml中的一个sql语句,而Configuration初始化的时候会加载所有的mapper接口类,而本篇再分析下是如何将mapper接口和xml进行绑定的。

先从上一篇的源码开始分析:

 1 public <T> void addMapper(Class<T> type) {
 2         if (type.isInterface()) {
 3           if (hasMapper(type)) {
 4             throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
 5           }
 6           boolean loadCompleted = false;
 7           try {
 8             knownMappers.put(type, new MapperProxyFactory<T>(type));//加载指定的mapper接口
 9             MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);//?
10             parser.parse();
11             loadCompleted = true;
12           } finally {
13             if (!loadCompleted) {
14               knownMappers.remove(type);
15             }
16           }
17         }
18       }

 

如果猜的没错的话,那么第9行和第10行就是解析xml并初始化MappedStatement对象的代码了。那么就先看看MapperAnnotationBuilder(mapper注解构造类)

MapperAnnotationBuilder类的构造方法如下:

 1  public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
 2     String resource = type.getName().replace('.', '/') + ".java (best guess)";
 3     this.assistant = new MapperBuilderAssistant(configuration, resource);
 4     this.configuration = configuration;
 5     this.type = type;
 6 
 7     sqlAnnotationTypes.add(Select.class);
 8     sqlAnnotationTypes.add(Insert.class);
 9     sqlAnnotationTypes.add(Update.class);
10     sqlAnnotationTypes.add(Delete.class);
11 
12     sqlProviderAnnotationTypes.add(SelectProvider.class);
13     sqlProviderAnnotationTypes.add(InsertProvider.class);
14     sqlProviderAnnotationTypes.add(UpdateProvider.class);
15     sqlProviderAnnotationTypes.add(DeleteProvider.class);
16   }

 

MapperAnnotationBuilder类的主要属性有:

1     private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();//sql语句上的注解
2     private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();//指定类指定方法上的sql语句注解
3 
4     private Configuration configuration;//全局配置对象
5     private MapperBuilderAssistant assistant;//Mapper构建助手类,组装解析出来的配置,生成Cache、ResultMap以及MappedStatement对象
6     private Class<?> type;//解析的目标mapper接口的Class对象

 

MapperAnnotationBuilder构造方法就是给自己的属性进行了初始化,其中两个Set集合在初始化时表明了支持 @Select、@Insert、@Update、@Delete以及对应的Provider注解,本篇不再介绍,和xml配置的写法只是写法上不一样而已,实际的作用是一样,本文只分析xml这种用法。

由第一段代码可知,MapperAnnotationBuilder有一个parse方法,也是创建MappedStatement的方法,源码如下:

 1 public void parse() {
 2     String resource = type.toString();//mapper接口名,如:interface com.lucky.test.mapper.UserMapper
 3     //Configuration中Set<String> loadedResources 保存着已经加载过的mapper信息
 4     if (!configuration.isResourceLoaded(resource)) {//判断是否已经加载过
 5       loadXmlResource();//加载XML资源
 6       configuration.addLoadedResource(resource);//加载的resource添加到Configuration的loadedResources中
 7       assistant.setCurrentNamespace(type.getName());
 8       parseCache();
 9       parseCacheRef();
10       Method[] methods = type.getMethods();//遍历mapper的所有方法
11       for (Method method : methods) {
12         try {
13           // issue #237
14           if (!method.isBridge()) {
15             parseStatement(method);
16           }
17         } catch (IncompleteElementException e) {
18           configuration.addIncompleteMethod(new MethodResolver(this, method));
19         }
20       }
21     }
22     parsePendingMethods();
23   }

 

 可见xml加载是通过调用loadXmlResource方法,源码如下:

 1 private void loadXmlResource() {
 2     // Spring may not know the real resource name so we check a flag
 3     // to prevent loading again a resource twice
 4     // this flag is set at XMLMapperBuilder#bindMapperForNamespace
 5     if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
 6       String xmlResource = type.getName().replace('.', '/') + ".xml";//通过mapper接口名找到对应的mapper.xml路径
 7       InputStream inputStream = null;
 8       try {
 9         //读取mapper.xml文件流
10         inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
11       } catch (IOException e) {
12         // ignore, resource is not required
13       }
14       if (inputStream != null) {
15         //根据mapper.xml文件流创建XMLMapperBuilder对象
16         XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
17         //执行parse方法
18         xmlParser.parse();
19       }
20     }
21   }

这里又涉及到了XMLMapperBuilder类,很明显是mapper.xml构建者类,构造方法源码如下:

 1   public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
 2     this(inputStream, configuration, resource, sqlFragments);//调用下面一个构造方法
 3     this.builderAssistant.setCurrentNamespace(namespace);
 4   }
 5 
 6   public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
 7     this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
 8         configuration, resource, sqlFragments);//调用下面一个构造方法
 9   }
10 
11   private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
12     //对自己的属性进行赋值
13     super(configuration);
14     this.builderAssistant = new MapperBuilderAssistant(configuration, resource);//mapper构造者助手
15     this.parser = parser;//XML解析类
16     this.sqlFragments = sqlFragments;//sql片段集合
17     this.resource = resource;//mapper名称
18   }

那么再看下parse方法,源码如下:

 1 public void parse() {
 2     if (!configuration.isResourceLoaded(resource)) {
 3       configurationElement(parser.evalNode("/mapper"));//从XML解析类中获取mapper标签的内容
 4       configuration.addLoadedResource(resource);//将加载过的resource添加到Configuration中
 5       bindMapperForNamespace();//绑定mapper到对应mapper的命名空间中
 6     }
 7 
 8     parsePendingResultMaps();
 9     parsePendingChacheRefs();
10     parsePendingStatements();
11   }

 

而解析xml的主要方法肯定就是configurationElement方法了,源码如下:

 1 private void configurationElement(XNode context) {
 2     try {
 3       String namespace = context.getStringAttribute("namespace");//从xml中获取namespace标签内容
 4       if (namespace == null || namespace.equals("")) {
 5         throw new BuilderException("Mapper's namespace cannot be empty");
 6       }
 7       builderAssistant.setCurrentNamespace(namespace);
 8       cacheRefElement(context.evalNode("cache-ref"));//获取cache-ref标签内容
 9       cacheElement(context.evalNode("cache"));//获取cache标签内容
10       parameterMapElement(context.evalNodes("/mapper/parameterMap"));//获取parameterMap内容
11       resultMapElements(context.evalNodes("/mapper/resultMap"));//获取resultMap内容
12       sqlElement(context.evalNodes("/mapper/sql"));//获取sql标签内容
13       buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析获取各种sql语句标签内容
14     } catch (Exception e) {
15       throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
16     }
17   }

很显然这个方法就是将xml中所有可能存在的标签都进行了解析处理,本文就不再详细分析,留得青山在不愁没柴烧。既然知道了xml的各个标签是在哪里解析的,就不愁不知道具体是怎么解析的了。分析到这里,似乎还是没有看到MappedStatement的身影,别急,让我们回到MapperAnnotationBuilder

的parse方法,先是通过loadXmlResource方法进行xml文件的解析加载,然后添加已经加载过的resource到Configuration中,然后解析二级缓存的配置,然后就是获取mapper接口的所有方法,遍历解析:

 1 loadXmlResource();
 2       configuration.addLoadedResource(resource);
 3       assistant.setCurrentNamespace(type.getName());
 4       parseCache();
 5       parseCacheRef();
 6       Method[] methods = type.getMethods();
 7       for (Method method : methods) {
 8         try {
 9           // issue #237
10           if (!method.isBridge()) {
11             parseStatement(method);
12           }
13         } catch (IncompleteElementException e) {
14           configuration.addIncompleteMethod(new MethodResolver(this, method));
15         }
16       }

 

上面红色标注的方法是获取方法的属性,然后将xml中配置的标签进行一一匹配,源码如下:

 1  void parseStatement(Method method) {
 2     Class<?> parameterTypeClass = getParameterType(method);
 3     LanguageDriver languageDriver = getLanguageDriver(method);
 4     SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
 5     if (sqlSource != null) {
 6       Options options = method.getAnnotation(Options.class);
 7       final String mappedStatementId = type.getName() + "." + method.getName();
 8       Integer fetchSize = null;
 9       Integer timeout = null;
10       StatementType statementType = StatementType.PREPARED;
11       ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
12       SqlCommandType sqlCommandType = getSqlCommandType(method);
13       boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
14       boolean flushCache = !isSelect;
15       boolean useCache = isSelect;
16 
17       KeyGenerator keyGenerator;
18       String keyProperty = "id";
19       String keyColumn = null;
20       if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
21         // first check for SelectKey annotation - that overrides everything else
22         SelectKey selectKey = method.getAnnotation(SelectKey.class);
23         if (selectKey != null) {
24           keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
25           keyProperty = selectKey.keyProperty();
26         } else if (options == null) {
27           keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
28         } else {
29           keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
30           keyProperty = options.keyProperty();
31           keyColumn = options.keyColumn();
32         }
33       } else {
34         keyGenerator = new NoKeyGenerator();
35       }
36 
37       if (options != null) {
38         if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
39           flushCache = true;
40         } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
41           flushCache = false;
42         }
43         useCache = options.useCache();
44         fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
45         timeout = options.timeout() > -1 ? options.timeout() : null;
46         statementType = options.statementType();
47         resultSetType = options.resultSetType();
48       }
49 
50       String resultMapId = null;
51       ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
52       if (resultMapAnnotation != null) {
53         String[] resultMaps = resultMapAnnotation.value();
54         StringBuilder sb = new StringBuilder();
55         for (String resultMap : resultMaps) {
56           if (sb.length() > 0) {
57             sb.append(",");
58           }
59           sb.append(resultMap);
60         }
61         resultMapId = sb.toString();
62       } else if (isSelect) {
63         resultMapId = parseResultMap(method);
64       }
65 
66       assistant.addMappedStatement(
67           mappedStatementId,
68           sqlSource,
69           statementType,
70           sqlCommandType,
71           fetchSize,
72           timeout,
73           // ParameterMapID
74           null,
75           parameterTypeClass,
76           resultMapId,
77           getReturnType(method),
78           resultSetType,
79           flushCache,
80           useCache,
81           // TODO gcode issue #577
82           false,
83           keyGenerator,
84           keyProperty,
85           keyColumn,
86           // DatabaseID
87           null,
88           languageDriver,
89           // ResultSets
90           options != null ? nullOrEmpty(options.resultSets()) : null);
91     }
92   }
View Code

 

最终是调用MapperBuilderAssistant对象的addMappedStatement方法创建了MappedStatement对象,源码如下:

 1 public MappedStatement addMappedStatement(
 2       String id,
 3       SqlSource sqlSource,
 4       StatementType statementType,
 5       SqlCommandType sqlCommandType,
 6       Integer fetchSize,
 7       Integer timeout,
 8       String parameterMap,
 9       Class<?> parameterType,
10       String resultMap,
11       Class<?> resultType,
12       ResultSetType resultSetType,
13       boolean flushCache,
14       boolean useCache,
15       boolean resultOrdered,
16       KeyGenerator keyGenerator,
17       String keyProperty,
18       String keyColumn,
19       String databaseId,
20       LanguageDriver lang,
21       String resultSets) {
22 
23     if (unresolvedCacheRef) {
24       throw new IncompleteElementException("Cache-ref not yet resolved");
25     }
26 
27     id = applyCurrentNamespace(id, false);
28     boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
29 
30     MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
31         .resource(resource)
32         .fetchSize(fetchSize)
33         .timeout(timeout)
34         .statementType(statementType)
35         .keyGenerator(keyGenerator)
36         .keyProperty(keyProperty)
37         .keyColumn(keyColumn)
38         .databaseId(databaseId)
39         .lang(lang)
40         .resultOrdered(resultOrdered)
41         .resultSets(resultSets)
42         .resultMaps(getStatementResultMaps(resultMap, resultType, id))
43         .resultSetType(resultSetType)
44         .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
45         .useCache(valueOrDefault(useCache, isSelect))
46         .cache(currentCache);
47 
48     ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
49     if (statementParameterMap != null) {
50       statementBuilder.parameterMap(statementParameterMap);
51     }
52 
53     MappedStatement statement = statementBuilder.build();
54     configuration.addMappedStatement(statement);
55     return statement;
56   }
View Code

 

创建了MappedStatement对象之后,就调用了Configuration的addMappedStatement方法,从而添加到了Configuration中的Map<String, MappedStatement> mappedStatements集合中,而key就是MappedStatement的id

 

而到目前为止,MappedStatement对象虽然被创建了,也已经加入到了Configuration管理的集合中去了,可是还没有和mapper接口建立上直接的关系。那么这两个又是如何最终联系到一起去的呢?下一篇再慢慢分析。

推荐阅读