Mybatis映射器原理分析

映射器是MyBatis中最核心的组件之一,他的作用是告诉MyBatis 到哪里去找SQL语句。在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁

前言

XMLConfigBuilderparseConfiguration方法中的mapperElement(root.evalNode("mappers"));代码片段开始

public class XMLConfigBuilder extends BaseBuilder {

   //mapperElement(root.evalNode("mappers"));
   private void mapperElement(XNode parent) throws Exception {
      if (parent != null) {
       for (XNode child : parent.getChildren()) {
        //package标签解析
        if ("package".equals(child.getName())) {
           //获取package标签属性name名字
          String mapperPackage = child.getStringAttribute("name");
          // 使用注解方式解析,这是sql映射处理,后面单独写篇文章说明
          configuration.addMappers(mapperPackage);
        } 
      
        //非package标签解析 
        //resource,url,class属性3者只能存在一个,否则全都不解析
        else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            // resource属性存在,url和class属性不存在,使用xml方式解析
            ErrorContext.instance().resource(resource);
            //获取资源文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //使用xml方式解析SQL,,这是sql映射处理,后面单独写篇文章说明
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
           // url属性存在,resource和class属性不存在,使用xml方式解析
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            //使用xml方式解析SQL,,这是sql映射处理,后面单独写篇文章说明
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
             class属性存在,resource和url属性不存在,使用注解方式方式解析
            Class<?> mapperInterface = Resources.classForName(mapperClass);
             // 使用注解方式解析SQL,这是sql映射处理,后面单独写篇文章说明
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("映射器元素只能指定url、资源或类,但不能指定多个.");
          }
        }
      }
    }
  }

mapper节点的属性,resourceurlclass接口3者只能存在一个,否则全都不解析。

一、mapper属性class映射器逻辑

class映射器逻辑如果使用xml方式解析,但是前提是其xml名字必须和其接口文件在同一路径下其名字相同。如果使用注解方式解析sql,那么可以直接使用@Select等注解

  <mappers>
      <mapper class="com.clyu.mapper.PersonMapper"/>
  </mappers>
1.1 案例

方式1 : 使用xml方式,其xml和接口在同一路径下
在这里插入图片描述

public interface PersonMapper {
  Person getOne(String id);
}

方式 2 : 使用注解方式,没有xml

public interface PersonMapper {

  @Select("select * from person where  id = #{id}")
  Person getOne( String id);

}
1.2 源码分析

分析从这里开始configuration.addMapper(mapperInterface);,其底层是MapperRegistryaddMapper(Class<T> type) 方法

public class MapperRegistry {

  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<>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //在从这里进入
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}


public class MapperAnnotationBuilder {
  public void parse() {
    String resource = type.toString();
    //如果之前解析了xml  这里就不会在解析了
    if (!configuration.isResourceLoaded(resource)) {
       //获取同路径下同名的xml文件,如果有使用xml方式解析。
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      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();
  }
}
二、mapper属性resource映射器逻辑

resource映射器逻辑是提高流获取映射文件信息, 其xml位置可以自定义
其也支持xml和注解方式,但是其在使用注解方式时,必须要有xml

<mappers>
    <mapper resource="mapper/PersonMapper.xml"/>
</mappers>
2.1 案例

方式1 : 使用xml方式,其xml位置可以自定义

<select id="getOne" resultType="com.clyu.bean.Person">
    select * from person where  id = #{id}
</select>
public interface PersonMapper {
  Person getOne( String id);
}

方式2 : 使用注解方式,必须有个xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.clyu.mapper.PersonMapper">

</mapper>
public interface PersonMapper {

  @Select("select * from person where  id = #{id}")
  Person getOne( String id);

}
2.2 源码分析
public class XMLMapperBuilder extends BaseBuilder {
	public void parse() {
	    if (!configuration.isResourceLoaded(resource)) {
	      //解析xml文件,拿到里面配置的配置项 最终封装成一个MapperedStatemanet
	      configurationElement(parser.evalNode("/mapper"));
	      //放到已解析资源池中
	      configuration.addLoadedResource(resource);
	      //解析绑定Namespace里面的Class对象
	      bindMapperForNamespace();
	    }
	    //重新解析之前解析不了的节点
	    parsePendingResultMaps();
	    parsePendingCacheRefs();
	    parsePendingStatements();
	  }
  }

结论:resource映射器逻辑是提高流获取映射文件信息,其只支持XML解析

三、mapper属性url映射器逻辑

url映射器逻辑是提高流获取映射文件信息,其只支持XML解析, 其xml位置可以自定义
resource映射器一般是获取classPath下的xml,而url映射器一般是获取磁盘或者网络资源上的xml文件

<mappers>
        <mapper url="file:Users/yuchaolei/Desktop/learnning/log/src/main/resources/person.xml"/>
</mappers>
3.1 案例

方式1 : 纯xml方式

<select id="getOne" resultType="com.clyu.bean.Person">
    select * from person where  id = #{id}
</select>
public interface PersonMapper {
  Person getOne( String id);
}

方式2 : 使用注解方式,必须有个xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.clyu.mapper.PersonMapper">

</mapper>
public interface PersonMapper {

  @Select("select * from person where  id = #{id}")
  Person getOne( String id);

}
3.2 源码
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //绑定Namespace里面的Class对象
      bindMapperForNamespace();
    }
    //重新解析之前解析不了的节点
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
四、package映射器逻辑

class映射器的批量操作就是package映射器

<mappers>
    <package name="com.clyu.mapper"/>
</mappers>
4.1 案例

方式1 : 使用xml方式,其xml和接口在同一路径下
在这里插入图片描述

public interface PersonMapper {
  Person getOne(String id);
}

方式 2 : 使用注解方式,没有xml

public interface PersonMapper {

  @Select("select * from person where  id = #{id}")
  Person getOne( String id);

}
4.2 源码
public class MapperRegistry {

  public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    //获取当前包下的所有类
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    //遍历他们
    for (Class<?> mapperClass : mapperSet) {
       //其底层逻辑和class映射器逻辑一样
      addMapper(mapperClass);
    }
  }
}

看了这几个映射器的源码,其实都大同小异,其都是为了解析mapper接口和其对于的xml(如果有的话),只不过class和package是直接获取mapper接口的位置,而resource和url映射器是通过xml的namespace属性获取mapper接口的位置。这也是为什么resource和url映射器在使用注解方式时,必须有xml

五、@Mapper注解作用
5.1 纯mybatis环境

mybatis版本3.5.3

  <mappers>
      <mapper class="com.clyu.mapper.PersonMapper"/>
  </mappers>
public interface PersonMapper {

  @Select("select * from person where  id = #{id}")
  Person getOne( String id);
  
}

测试输出

DEBUG [main] - ==>  Preparing: select * from person where id = ? 
DEBUG [main] - ==> Parameters: 1(String)
DEBUG [main] - <==      Total: 1

如果我们去掉配置文件中的 <mappers><mapper class="com.clyu.mapper.PersonMapper"/> </mappers>,PersonMapper接口修改如下

@Mapper
public interface PersonMapper {

  @Select("select * from person where  id = #{id}")
  Person getOne( String id);

}

测试输出

Exception in thread "main" org.apache.ibatis.binding.BindingException: 
Type interface com.clyu.mapper.PersonMapper is not known to the MapperRegistry.

结论:在纯Mybatis项目中@Mapper不能开启注解模式,如果想开启Mybatis注解方式,就必须在Mybatis配置文件中配置映射器,在纯Mybatis项目中他的作用仅是标记MyBatis映射器的接口

注意:在SSM项目中,开启注解模式也与是否标注@Mapper注解无关

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值