MyBatis启动:MapperStatement创建

参考:http://blog.csdn.net/ashan_li/article/details/50351080

MappedStatement说明

一个MappedStatement对象对应Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条SQL语句。其属性有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//节点中的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; 
private List<ResultMap> resultMaps; 
private boolean flushCacheRequired; 
private boolean useCache; 
private boolean resultOrdered; 
//SQL的类型,select/update/insert/detete 
private SqlCommandType sqlCommandType; 
private KeyGenerator keyGenerator; 
private String[] keyProperties; 
private String[] keyColumns; 
   
//是否有内映射 
private boolean hasNestedResultMaps; 
private String databaseId; 
private Log statementLog; 
private LanguageDriver lang; 
private String[] resultSets;

  

Mapper是接口,用来声明持久层的方法,而Mapper配置对应的XML,决定了方法的执行的内容,决定持久层方法的行为。在MyBatis启 动时,会解析这些包含SQL的XML文件,并将其包装成为MapperStatement对象,并将MapperStatement注册到全局的 configuration对象上,接下来就深入的了解代码的实现。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void mapperElement(XNode parent) throws Exception { 
    if (parent != null) { 
      for (XNode child : parent.getChildren()) { 
        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"); 
          if (resource != null && url == null && mapperClass == null) { 
            ErrorContext.instance().resource(resource); 
            InputStream inputStream = Resources.getResourceAsStream(resource); 
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 
            mapperParser.parse(); 
          } else if (resource == null && url != null && mapperClass == null) { 
            ErrorContext.instance().resource(url); 
            InputStream inputStream = Resources.getUrlAsStream(url); 
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 
            mapperParser.parse(); 
          } else if (resource == null && url == null && mapperClass != null) { 
            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."); 
          
        
      
    
  

  

从 源码中就可以看出,配置Mapper时,可以配置package熟悉,注册包下所有的接口。还可以从资源中比如硬盘上,网络中,去加载XML文件。注册过 程是通过注册器MapperRegistry来完成的。注册的容器是一个map,Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();。

 

key是mapper的接口完整类名,value是mapper的代理工厂。注册完成后,还要做解析XML文件操作。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
      knownMappers.put(type, new MapperProxyFactory<T>(type)); 
      // 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); 
      
    
  
}

  

 

  

下面 是解析的代码

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void parse() { 
    String resource = type.toString(); 
    if (!configuration.isResourceLoaded(resource)) { 
      loadXmlResource(); 
      configuration.addLoadedResource(resource); 
      assistant.setCurrentNamespace(type.getName()); 
      parseCache(); 
      parseCacheRef(); 
      Method[] methods = type.getMethods(); 
      for (Method method : methods) { 
        try
          parseStatement(method); 
        } catch (IncompleteElementException e) { 
          configuration.addIncompleteMethod(new MethodResolver(this, method)); 
        
      
    
    parsePendingMethods(); 
  

  

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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"
    InputStream inputStream = null
    try
      inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); 
    } catch (IOException e) { 
      // ignore, resource is not required 
    
    if (inputStream != null) { 
      XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); 
      xmlParser.parse(); 
    
  

  

 

 

MyBatis通过替换mapper完整类名中的“.”,替换成为“/”,然后加上后缀“.xml”,拼成XML资源路径,然后判断是否已加载过XML,没有的话加载XML文件,然后使用xmlMapperBuilder建造者解析XML中的元素。


 
1
2
3
4
5
6
7
8
9
10
11
public void parse() { 
  if (!configuration.isResourceLoaded(resource)) { 
    configurationElement(parser.evalNode("/mapper")); 
    configuration.addLoadedResource(resource); 
    bindMapperForNamespace(); 
  
   
  parsePendingResultMaps(); 
  parsePendingChacheRefs(); 
  parsePendingStatements(); 

  

resource是创建建造者的构造参数,type.getClass(),就是mapper的类型。判断然后还没有加载mapper,就开始解析XML文件中的mapper节点。

 

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

  

 

 

解 析时,先设置命名空间。然后解析cache-ref元素,可以使用其他命名空间的的缓存。在configuration对象上有一个 cacheRefMap用来维护引用缓存的关系。并且引用其他命名空间的引用指向助手类的currCache属性上。如果被指向的命名空间还未加载,则抛 出异常,并且往configuration对象上添加未处理的缓存引用chcheRef。

1
2
3
4
5
6
7
8
9
10
11
private void cacheRefElement(XNode context) { 
  if (context != null) { 
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); 
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); 
    try
      cacheRefResolver.resolveCacheRef(); 
    } catch (IncompleteElementException e) { 
      configuration.addIncompleteCacheRef(cacheRefResolver); 
    
  

  

 

解析缓存元素,可以使用type属性配置自定义的缓存,否则使用默认 的PERPETUAL。然后用别名注册器注册缓存类。接下来注册缓存的回收算法,缓存大小,过期时间,是否只读等属性。然后由助手类通过反射创建一个具体 的Cache对象。然后注册到configuration全局对象上。

1
2
3
4
5
6
7
8
9
10
11
12
13
private void cacheElement(XNode context) throws Exception { 
  if (context != null) { 
    String type = context.getStringAttribute("type", "PERPETUAL"); 
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); 
    String eviction = context.getStringAttribute("eviction", "LRU"); 
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); 
    Long flushInterval = context.getLongAttribute("flushInterval"); 
    Integer size = context.getIntAttribute("size"); 
    boolean readWrite = !context.getBooleanAttribute("readOnly", false); 
    Properties props = context.getChildrenAsProperties(); 
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props); 
  

  

 

 

下一步是解析parameterMap,新版中已经不推荐配置这个属性了,属于老方法。

参数Map映射已经被淘汰,但是结果集映射还很有用。接下来就是解析 resultMap。解析resultMap的元素比较多,解析完成后,还会根据解析到的映射关系创建一个结果处理器对象 resultMapResolver,后面对数据库操作时,用来处理列和属性的类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { 
  ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); 
  String id = resultMapNode.getStringAttribute("id"
      resultMapNode.getValueBasedIdentifier()); 
  String type = resultMapNode.getStringAttribute("type"
      resultMapNode.getStringAttribute("ofType"
          resultMapNode.getStringAttribute("resultType"
              resultMapNode.getStringAttribute("javaType")))); 
  String extend = resultMapNode.getStringAttribute("extends"); 
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); 
  Class<?> typeClass = resolveClass(type); 
  Discriminator discriminator = null
  List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); 
  resultMappings.addAll(additionalResultMappings); 
  List<XNode> resultChildren = resultMapNode.getChildren(); 
  for (XNode resultChild : resultChildren) { 
    if ("constructor".equals(resultChild.getName())) { 
      processConstructorElement(resultChild, typeClass, resultMappings); 
    } else if ("discriminator".equals(resultChild.getName())) { 
      discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); 
    } else
      ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>(); 
      if ("id".equals(resultChild.getName())) { 
        flags.add(ResultFlag.ID); 
      
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); 
    
  
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); 
  try
    return resultMapResolver.resolve(); 
  } catch (IncompleteElementException  e) { 
    configuration.addIncompleteResultMap(resultMapResolver); 
    throw e; 
  

  

 

 

解析来继续解析SQL片段,用来复用的SQL。助手类会将SQL片段的ID前面加上当前命名空间和一个点,用来和其他命名空间区别开。然后将SQL片段加载到configuration全局对象的sqlFragments对象上保存。

 

 

1
2
3
4
5
6
7
8
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception { 
  for (XNode context : list) { 
    String databaseId = context.getStringAttribute("databaseId"); 
    String id = context.getStringAttribute("id"); 
    id = builderAssistant.applyCurrentNamespace(id, false); 
    if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context); 
  

  

 

 

最后是重头戏,解析增删改查节点,创建Statement对象。同样是通过建造者模式来创建语句对象,建造者的构造参数包括全局配置信息,当前命名空间助手,XML配置信息和数据库ID。

1
2
3
4
5
6
7
8
9
10
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { 
  for (XNode context : list) { 
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); 
    try
      statementParser.parseStatementNode(); 
    } catch (IncompleteElementException e) { 
      configuration.addIncompleteStatement(statementParser); 
    
  

  

 

首先还是解析XML文件的各个属性,然后处理<include>和<selectKey>片段。根据include标签中的refid到全局配置中取对应的SQL片段。根据selectKey的配置信息,创建一个MapperStatement,并且添加到全局配置中,然后移除selectKey节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public void parseStatementNode() { 
  String id = context.getStringAttribute("id"); 
  String databaseId = context.getStringAttribute("databaseId"); 
   
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return
   
  Integer fetchSize = context.getIntAttribute("fetchSize"); 
  Integer timeout = context.getIntAttribute("timeout"); 
  String parameterMap = context.getStringAttribute("parameterMap"); 
  String parameterType = context.getStringAttribute("parameterType"); 
  Class<?> parameterTypeClass = resolveClass(parameterType); 
  String resultMap = context.getStringAttribute("resultMap"); 
  String resultType = context.getStringAttribute("resultType"); 
  String lang = context.getStringAttribute("lang"); 
  LanguageDriver langDriver = getLanguageDriver(lang); 
   
  Class<?> resultTypeClass = resolveClass(resultType); 
  String resultSetType = context.getStringAttribute("resultSetType"); 
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); 
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 
   
  String nodeName = context.getNode().getNodeName(); 
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); 
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT; 
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); 
  boolean useCache = context.getBooleanAttribute("useCache", isSelect); 
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); 
   
  // Include Fragments before parsing 
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); 
  includeParser.applyIncludes(context.getNode()); 
   
  // Parse selectKey after includes and remove them. 
  processSelectKeyNodes(id, parameterTypeClass, langDriver); 
     
  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 
  String resultSets = context.getStringAttribute("resultSets"); 
  String keyProperty = context.getStringAttribute("keyProperty"); 
  String keyColumn = context.getStringAttribute("keyColumn"); 
  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)) 
        ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); 
  
   
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, 
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, 
      resultSetTypeEnum, flushCache, useCache, resultOrdered,  
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); 

  

 

 

接下来的操作,也是根据配置的属性,然后通过建造者创建mappedStatement对象。并添加到configuration全局对象上。

posted @   QiaoZhi  阅读(3359)  评论(0编辑  收藏  举报
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示