上一篇我们了解到了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

相关文章:

  • 2021-11-05
  • 2022-12-23
  • 2022-12-23
  • 2021-10-14
  • 2021-11-15
  • 2021-08-03
  • 2021-11-06
  • 2022-12-23
猜你喜欢
  • 2021-06-02
  • 2021-07-30
  • 2022-12-23
  • 2021-10-21
  • 2021-04-01
相关资源
相似解决方案