mybatis源碼解析7---MappedStatement初始化過程


上一篇我們了解到了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接口建立上直接的關系。那么這兩個又是如何最終聯系到一起去的呢?下一篇再慢慢分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM