mybatis源码-解析mappers标签

文章详细介绍了MyBatis框架中解析mappers标签的过程,包括解析package子标签以扫描指定包下的mapper接口,以及解析mapper子标签直接引用的XML映射文件。解析过程中涉及到了Configuration类、MapperRegistry类、MapperProxyFactory类等多个关键组件,解释了如何将mapper接口与XML映射文件绑定并构建MappedStatement对象。
摘要由CSDN通过智能技术生成

解析mappers标签下的子标签(package)

	<mappers>
        <package name="com.example.maybatissource.dao"/>
    </mappers>

package标签配置的是一个包的路径,name表示的是mapper接口路径。

public class XMLConfigBuilder extends BaseBuilder {
	//省略其他代码
	private void mapperElement(XNode parent) throws Exception {
	    if (parent != null) {
	      for (XNode child : parent.getChildren()) {
	        if ("package".equals(child.getName())) {
	          //获得name属性即包名
	          String mapperPackage = child.getStringAttribute("name");
	          configuration.addMappers(mapperPackage);
	        } else {
	        	//省略其他代码
	        }
	      }
	    }
  	}
}

点开if条件是package的configuration.addMappers(mapperPackage);这条语句
解析包下所有资源
当前在Configuration类:

public class Configuration {
	 //省略其他代码
	 protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
	 
	 public void addMappers(String packageName) {
	    mapperRegistry.addMappers(packageName);
	  }
}

当前在MapperRegistry类:
解析包下的所有接口类,并将mapper接口类封装为MapperProxyFactory对象,并放入knownMappers中,接着对mapper接口类进行解析,如果解析失败会把刚才放入knownMappers中的值从knownMappers中移除

public class MapperRegistry {

	private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();	

	//packageName(包名)
	public void addMappers(String packageName) {
    	addMappers(packageName, Object.class);
  	}
  	
  	//packageName(包名),superType(Object类)
  	public void addMappers(String packageName, Class<?> superType) {
  		//解析packageName下的所有class文件
	    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
	    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
	    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
	    for (Class<?> mapperClass : mapperSet) {
	      addMapper(mapperClass);
	    }
  	}
  	
  	public <T> void addMapper(Class<T> type) {
  		//判断这个Class是否是接口
	    if (type.isInterface()) {
	      //如果有就报错(其实判断就是knownMappers是否有该mapper接口)
	      if (hasMapper(type)) {
	        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
	      }
	      //用变量来控制后面的解析
	      boolean loadCompleted = false;
	      try {
	      	//key是mapper(type)接口类,value是mapper代理工厂(MapperProxyFactory)
	        knownMappers.put(type, new MapperProxyFactory<>(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.
	        
	        //对mapper文件进行解析
	        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
	        //具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息
	        parser.parse();
	        loadCompleted = true;
	      } finally {
	      	//如果解析出错就删除knownMappers中的值
	        if (!loadCompleted) {
	          knownMappers.remove(type);
	        }
	      }
	    }
	 }
	 
	 //判断knownMappers中是否有当前的Class<T> type
	 public <T> boolean hasMapper(Class<T> type) {
    	return knownMappers.containsKey(type);
  	 }
}

ResolverUtil类的解析可以看我写的这篇文章
https://blog.csdn.net/qq_40913932/article/details/113753455

当前在MapperProxyFactory类:

public class MapperProxyFactory<T> {
	private final Class<T> mapperInterface;
  	private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  	public MapperProxyFactory(Class<T> mapperInterface) {
    	this.mapperInterface = mapperInterface;
  	}
}

当前在MapperAnnotationBuilder类:

public class MapperAnnotationBuilder {
	private final Configuration configuration;
	private final MapperBuilderAssistant assistant;
	private final Class<?> type;
	
	public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
	    String resource = type.getName().replace('.', '/') + ".java (best guess)";
	    this.assistant = new MapperBuilderAssistant(configuration, resource);
	    this.configuration = configuration;
	    this.type = type;
	}
}

当前在MapperBuilderAssistant类:

public class MapperBuilderAssistant extends BaseBuilder {
	private String currentNamespace;
  	private final String resource;
  	private Cache currentCache;
  	private boolean unresolvedCacheRef; // issue #676
  	public MapperBuilderAssistant(Configuration configuration, String resource) {
	    super(configuration);
	    //将这个资源文件(resource)存储到ErrorContext异常类的resource中
	    ErrorContext.instance().resource(resource);
	    this.resource = resource;
  	}
}

接着来看看对mapper文件是如何进行解析:
调用了 parser.parse();方法,
当前在MapperAnnotationBuilder类:

public class MapperAnnotationBuilder {
	private final Configuration configuration;
	private final MapperBuilderAssistant assistant;
	private final Class<?> type;
	
	public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
	    String resource = type.getName().replace('.', '/') + ".java (best guess)";
	    this.assistant = new MapperBuilderAssistant(configuration, resource);
	    this.configuration = configuration;
	    this.type = type;
	}

	public void parse() {
		//拿到当前接口类
	    String resource = type.toString();
	    //isResourceLoaded在configuration类中是一个变量名是loadedResources的set集合
	    //判断是否有该mapper接口(resource),如果没有就添加
	    if (!configuration.isResourceLoaded(resource)) {
	      /*
	      解析和接口同名的xml文件,前提是存在该文件,
	      如果不存在该文件就解析接口中方法上的注解
	      最终要做的是把xml文件中的标签,转化为mapperStatement,
          并放入mappedStatements中
	      */
	      loadXmlResource();
	      //就添加resource到变量名是loadedResources的set集合
	      configuration.addLoadedResource(resource);
	      assistant.setCurrentNamespace(type.getName());
	      //解析接口上的@CacheNamespace注解
	      parseCache();
	      parseCacheRef();
	      //获得接口中的所有方法,并解析方法上的注解
	      Method[] methods = type.getMethods();
	      for (Method method : methods) {
	        try {
	          // issue #237
	          if (!method.isBridge()) {
	          	//解析方法上的注解
	            parseStatement(method);
	          }
	        } catch (IncompleteElementException e) {
	          configuration.addIncompleteMethod(new MethodResolver(this, method));
	        }
	      }
	    }
	    parsePendingMethods();
  	}
  
}

上面的解析分为两个过程,首先解析对应的XML映射文件,再解析方法上的注解。
先看XML是如何解析:

public class MapperAnnotationBuilder {
	private void loadXmlResource() {
		//判断变量名是loadedResources的set集合是否有该mapper.xml
	    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
	      //解析对应的XML映射文件,其名称为接口类+"."+xml,即和接口类同名且在同一个包下。
	      String xmlResource = type.getName().replace('.', '/') + ".xml";
	      // #1347
	      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
	      if (inputStream == null) {
	        // Search XML mapper that is not in the module but in the classpath.
	        try {
	          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
	        } catch (IOException e2) {
	          // ignore, resource is not required
	        }
	      }
	      //输入流存在就开始解析xml
	      if (inputStream != null) {
	        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
	        //解析mapper.xml文件(是你写的sql的xml文件)
	        xmlParser.parse();
	      }
	    }
  }
}

确定XML映射文件的位置,和接口类同名且在同一个包下。如下的例子:
1.0
解析xml文件:

public class XMLMapperBuilder extends BaseBuilder {
	//省略其他代码
	public void parse() {
	//判断是否加载过该资源,根据变量名是loadedResources的set集合是否有该mapper
    if (!configuration.isResourceLoaded(resource)) {
      //解析mapper.xml文件中的<mapper>标签及其子标签
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
}

解析<mapper>标签里的sql
当前在XMLMapperBuilder类中

public class XMLMapperBuilder extends BaseBuilder {
	private void configurationElement(XNode context) {
	    try {
	      //获得<mapper>标签中namespace属性的值
	      String namespace = context.getStringAttribute("namespace");
	      if (namespace == null || namespace.equals("")) {
	        throw new BuilderException("Mapper's namespace cannot be empty");
	      }
	      //将namespace设置到currentNamespace中
	      builderAssistant.setCurrentNamespace(namespace);
	      //解析<cache-ref>标签
	      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"));
	      //解析select、insert、update、delete子标签
          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);
	    }
	  }
}

看看如何解析我们写的select|insert|update|delete。
当前在XMLMapperBuilder类中

public class XMLMapperBuilder extends BaseBuilder {
	//省略其他代码
	private void buildStatementFromContext(List<XNode> list) {
		//如果配置了<databaseIdProvider>,则使用自定义的,否则使用默认的
	    if (configuration.getDatabaseId() != null) {
	      buildStatementFromContext(list, configuration.getDatabaseId());
	    }
	    buildStatementFromContext(list, null);
	  }

	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);
	      }
	    }
	  }
}

调用XMLStatementBuilder的parseStatementNode方法
当前在XMLStatementBuilder 类中

public class XMLStatementBuilder extends BaseBuilder {
	//省略其他代码
	public void parseStatementNode() {
		//获取id
	    String id = context.getStringAttribute("id");
	    //获取databaseId
	    String databaseId = context.getStringAttribute("databaseId");
	
		//验证sql中设置的databaseId是否匹配<databaseIdProvider>标签的一样
	    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
	    //不符合当前数据源对应的数据厂商信息的sql语句不加载
	      return;
	    }
		//获取<select>|<insert>|<update>|<delete>的标签名
	    String nodeName = context.getNode().getNodeName();
	    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
	    //如果是查询语句
	    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
	    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
	    //查询语句默认开启一级缓存,这里默认是true
	    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
	    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
	
	    // Include Fragments before parsing
	     // 解析 <include>标签 属性
	    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
	    includeParser.applyIncludes(context.getNode());
	
		//参数类型;得到参数类的可以是完全限定名或别名。因为 MyBatis 可以通过 typeAliasRegistry去得到对应的参数类型,或者通过反射拿到具体的参数类型。
	    String parameterType = context.getStringAttribute("parameterType");
	    Class<?> parameterTypeClass = resolveClass(parameterType);
	
	 	// 获取 Mapper 语法类型
	    String lang = context.getStringAttribute("lang");
	    //如果为空,就使用XMLLanguageDriver
	    LanguageDriver langDriver = getLanguageDriver(lang);
	
	    // Parse selectKey after includes and remove them.
	    // 解析 selectKey 元素
	    processSelectKeyNodes(id, parameterTypeClass, langDriver);
	
	    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
	    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))
	          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
	    }
	
	    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
	    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
	    Integer fetchSize = context.getIntAttribute("fetchSize");
	    Integer timeout = context.getIntAttribute("timeout");
	    String parameterMap = context.getStringAttribute("parameterMap");
	    String resultType = context.getStringAttribute("resultType");
	    Class<?> resultTypeClass = resolveClass(resultType);
	    String resultMap = context.getStringAttribute("resultMap");
	    String resultSetType = context.getStringAttribute("resultSetType");
	    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
	    String keyProperty = context.getStringAttribute("keyProperty");
	    String keyColumn = context.getStringAttribute("keyColumn");
	    String resultSets = context.getStringAttribute("resultSets");
	
		//通过buildAssistant将解析得到的参数设置构造成MappedStatement对象
	    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
	        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
	        resultSetTypeEnum, flushCache, useCache, resultOrdered,
	        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
}

具体的sql标签意思可以参考这篇文章:https://www.w3cschool.cn/mybatis/f4uw1ilx.html

解析mappers标签下的子标签(mapper)

	<mappers>
        <mapper resource="mapper/student_table/StudentTableMapper.xml"/>
    </mappers>

配置mapper标签配置的是一个xml文件,该文件中存在相关的sql语句

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值