mapper解析
接着上篇的配置,本篇主要讲解mappers标签
该标签的主要功能:标记Mybatis数据库接口或Mybatis映射文件,配置方式有三:
数据库接口所在父包路径 数据库接口类路径 数据库映射文件路径 数据库映射文件资源路径
源码解析:org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XNode root)
private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // mappers 标签解析 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
进入到方法
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // 解析 mappers 的子节点 if ("package".equals(child.getName())) { // 配置接口扫描包 package 节点(扫描包) String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); // 如果配置了package节点,mybatis默认会将该包下所有的接口类加载到mybatis中 } else { // 单独配置 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { // resource xml 文件 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."); } } } } }
得到mappers节点后,解析其子节点。
如果子节点中存在 package 节点,则解析方式如下:
- 获取包路径 name属性值
- 解析:configuration.addMappers(mapperPackage);
public void addMappers(String packageName) { mapperRegistry.addMappers(packageName); } /** * @since 3.2.2 也就是mybatis3.2.2版本才增加了这一功能 */ public void addMappers(String packageName) { // 添加mappers,并指定要添加的类或子类的类型 addMappers(packageName, Object.class); } /** * @since 3.2.2 */ public void addMappers(String packageName, Class superType) { // 工具类:用来判断是否是指定类或其子类,并存储结果 ResolverUtil
> resolverUtil = new ResolverUtil >(); // 查找类 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set >> mapperSet = resolverUtil.getClasses(); for (Class mapperClass : mapperSet) { addMapper(mapperClass); } } public ResolverUtil find(Test test, String packageName) { // 获取包路径 String path = getPackagePath(packageName); try { // 获取包路径下的所有文件路径(格式:com/mysql/jdbc/jdbc2/optional/JDBC4SuspendableXAConnection.class) List children = VFS.getInstance().list(path); // 遍历包路径下的所有文件路径 for (String child : children) { // 过滤 class 文件 if (child.endsWith(".class")) { // 判断是否是指定类或指定类的子类 addIfMatching(test, child); } } } catch (IOException ioe) { log.error("Could not read package: " + packageName, ioe); } return this; } // 所以包路径的格式类似:com.xxx.xxx.xxx protected String getPackagePath(String packageName) { return packageName == null ? null : packageName.replace('.', '/'); } // 判断是否是指定类或指定类的子类 protected void addIfMatching(Test test, String fqn) { try { String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.'); ClassLoader loader = getClassLoader(); if (log.isDebugEnabled()) { log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); } // 得到类的Class类型 Class type = loader.loadClass(externalName); // 判断,符合存储到set集合中 if (test.matches(type)) { matches.add((Class ) type); } } catch (Throwable t) { log.warn("Could not examine class '" + fqn + "'" + " due to a " + t.getClass().getName() + " with message: " + t.getMessage()); } } // 添加 public void addMapper(Class 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 (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); } } } } 分析上面的代码:a、根据包名称转换得到包路径;b、根据包路径获取该包下所有的文件;c、遍历文件,取出class结尾的文件;d、判断是否是指定类或指定类的子类;e:添加并解析
-
具体的解析(非package节点的解析直接进入这):
public
void addMapper(Class 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 (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、之前未添加过haddMapper;3、存储:knownMappers.put(type, new MapperProxyFactory<T>(type));4、映射文件解析
-
映射文件解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();
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; sqlAnnotationTypes.add(Select.class); sqlAnnotationTypes.add(Insert.class); sqlAnnotationTypes.add(Update.class); sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class); sqlProviderAnnotationTypes.add(InsertProvider.class); sqlProviderAnnotationTypes.add(UpdateProvider.class); sqlProviderAnnotationTypes.add(DeleteProvider.class); }
public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { // 判断是否解析过 loadXmlResource(); // 解析映射文件 configuration.addLoadedResource(resource); // 添加已解析 assistant.setCurrentNamespace(type.getName()); // 校验映射文件命名空间 parseCache(); // 缓存解析 parseCacheRef(); // TODO 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(); // }
映射文件解析:loadXmlResource();
-
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(); } } }
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());xmlParser.parse();