Mybatis 基于注解Mapper源码分析

目前Mybatis除了可以通过XML配置SQL外还可以通过注解的形式配置SQL,本文中主要介绍了Mybatis是如何处理注解SQL映射的,通过源码分析处理过程

XML配置

<configuration>
	<settings>
		<setting name="defaultExecutorType" value="SIMPLE"/>
		<setting name="useGeneratedKeys" value="true"/>
	</settings>

	<typeAliases>
		<typeAlias type="org.apache.ibatis.submitted.blocking_cache.Person" alias="Person" />
	</typeAliases>

	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC">
				<property name="" value="" />
			</transactionManager>
			<dataSource type="UNPOOLED">
				<property name="driver" value="org.hsqldb.jdbcDriver" />
				<property name="url" value="jdbc:hsqldb:mem:cache" />
				<property name="username" value="sa" />
			</dataSource>
		</environment>
	</environments>

	<mappers>
		<mapper class="org.apache.ibatis.submitted.blocking_cache.PersonMapper"/>
	</mappers>
</configuration>

  解析过程

  private void mapperElement(XNode parent) throws Exception {
    //如果configuration中配置了mapper节点
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //如果配置对是包路径
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          //获取mapper元素中的resource属性
          String resource = child.getStringAttribute("resource");
          //获取mapper元素中的url属性
          String url = child.getStringAttribute("url");
          //获取mapper元素中的class属性,如果基于注解的配置的mapper 配置的就是class
          String mapperClass = child.getStringAttribute("class");
          //对于resource,url,mapperClass 优先使用resource,其次是url最后是class
          if (resource != null && url == null && mapperClass == null) {
            //创建异常上下文
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //创建XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //解析XML
            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) {
            //获取mapper的接口
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            //将该接口注册到已知mapper
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

  

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

  

//注册Mapper
  public <T> void addMapper(Class<T> type) {
    //如果type是接口
    if (type.isInterface()) {
      //判断该接口是否已经注册过相应的Mapper了,如果是则抛出异常,因为knownMappers的key为type
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //对该接口创建MapperProxyFactory,并保注册到knownMappers
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        //创建MapperAnnotationBuilder
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //解析Annotation
        parser.parse();
        //解析成功则表示加载完成
        loadCompleted = true;
      } finally {
        //如果加载没有完成则将其从knownMappers中删除
        if (!loadCompleted) {
          knownMappers.remove(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;  //初始化注解类型,分为两组  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)) {    //没有注册过则需要加载XML资源    loadXmlResource();    //将该资源名称添加到已经注册集合中    configuration.addLoadedResource(resource);    //设置nameSpace    assistant.setCurrentNamespace(type.getName());    //解析cache    parseCache();    //解析cacheRef    parseCacheRef();    //获取    Method[] methods = type.getMethods();    for (Method method : methods) {      try {        //如果不是bridge方法则解析statement        if (!method.isBridge()) {          parseStatement(method);        }      } catch (IncompleteElementException e) {        configuration.addIncompleteMethod(new MethodResolver(this, method));      }    }  }  //解析待定方法  parsePendingMethods();}

  

private void loadXmlResource() {
    //判断资源是否有加载过
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      //获取资源的path
      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
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        //解析XML
        xmlParser.parse();
      }
    }
  }

  

 private void parseCache() {
    //获取接口上 @CacheNamespace注解
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    //如果注解存在
    if (cacheDomain != null) {
      //获取注解配置的缓存大小
      Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
      //获取缓存刷新频率
      Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
      //解析CacheNamespace配置的属性
      Properties props = convertToProperties(cacheDomain.properties());
      //使用注解配置的数据创建缓存
      assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
    }
  }

  

private void parseCacheRef() {
    //获取接口上 @CacheNamespaceRef 注解
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
    //如果配置了缓存引用
    if (cacheDomainRef != null) {
      //获取引用的类型
      Class<?> refType = cacheDomainRef.value();
      String refName = cacheDomainRef.name();
      //如果引用类型和引用名称都为空则抛出异常
      if (refType == void.class && refName.isEmpty()) {
        throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
      }
      //如果引用类型和引用名称同时配置了有效数据则抛出异常,这两个是互斥数据
      if (refType != void.class && !refName.isEmpty()) {
        throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
      }
      //获取namespace
      String namespace = (refType != void.class) ? refType.getName() : refName;
      //使用缓存
      assistant.useCacheRef(namespace);
    }
  }

  

  void parseStatement(Method method) {
    //获取参数类型
    Class<?> parameterTypeClass = getParameterType(method);
    //获取LanguageDriver
    LanguageDriver languageDriver = getLanguageDriver(method);
    //从注解中获取Sqlsource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    //如果sqlSource不为null
    if (sqlSource != null) {
      //获取 @Options注解
      Options options = method.getAnnotation(Options.class);
      //创建statementId
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      //获取sql类型
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      //是否是查询
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      //是否需要刷新缓存,如果不是查询默认值为true,查询默认值为false
      boolean flushCache = !isSelect;
      //是否使用缓存,查询默认为true,不是查询默认为false
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = null;
      //如果是插入或更新
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        //获取SelectKey注解
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {//不是插入或更新 即查询和删除则keyGenerator为NoKeyGenerator实例
        keyGenerator = NoKeyGenerator.INSTANCE;
      }
      //如果@Options注解不为null
      if (options != null) {
        //根据配置的值设置是否刷新缓存
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        //是否使用缓存
        useCache = options.useCache();
        //fetchSize
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      String resultMapId = null;
      //获取ResultMap注解
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

  

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
      //获取@Select, @Insert, @Update, @Delete类型的注解
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      //获取 @SelectProvider, @InsertProvider, @UpdateProvider @DeleteProvider注解
      Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      //如果SQL注解不为null
      if (sqlAnnotationType != null) {
        //同时sqlProvider注解也不为空则抛出异常,两者互斥
        if (sqlProviderAnnotationType != null) {
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        //获取注解
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        //获取注解配置的值
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        //根据配置的数据创建SqlSource
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {//如果SqlProvider注解不为空
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        //创建一个ProviderSqlSource
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
      }
      //如果没有配置Sql注解也没有配置SqlProvider注解则返回null
      return null;
    } catch (Exception e) {
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }

  

原文地址:https://www.cnblogs.com/wei-zw/p/8908536.html

时间: 2024-08-29 10:57:33

Mybatis 基于注解Mapper源码分析的相关文章

Mybatis结合Spring注解自动扫描源码分析

作为一个想做架构师的程序员,必须是一个优秀的程序员,在引入某一个框架的时候,必须要研究源码,将新的开源框架的风险变为可控性. 1.Spring结合Mybatis最常用的配置. <!--理论加实践,才是架构师嘚最佳实践 --> <!--JDBC Data Source --> < bean id= "testdataSource" class= "org.springframework.jdbc.datasource.DriverManagerDa

mybatis之XML解析源码分析

一直想知道mybatis是如何解析xml文件的,今天认真看了下源码,这里记录一下 这里是用mybatis-spring的SqlSessionFactoryBean当作的入口,mybatis-spring其实很简单,源码也就几个看看就懂了,代理了一下而已没啥东东. 1.解析spring的配置 不过很多参数都是spring中来处理了,所以mybatis-spring没有先parse而是先加载了配置文件 依次是 typeAliasesPackage typeAliases Plugins typeHa

Mybatis中selectKey源码分析

刚回答了一个问题这样一个问题,mybatis不能正常返回主键增加值  下面通过源码分析一下selectKey都具体实现:关于Mybatis 基于注解Mapper源码分析 可以看一下具体解析过程. @Insert("insert into table2 (name) values(#{name})") @SelectKey(statement="call identity()", keyProperty="nameId", before=false

Spring IoC 源码分析 (基于注解) 之 包扫描

在上篇文章Spring IoC 源码分析 (基于注解) 一我们分析到,我们通过AnnotationConfigApplicationContext类传入一个包路径启动Spring之后,会首先初始化包扫描的过滤规则.那我们今天就来看下包扫描的具体过程. 还是先看下面的代码: AnnotationConfigApplicationContext类 //该构造函数会自动扫描以给定的包及其子包下的所有类,并自动识别所有的Spring Bean,将其注册到容器中 public AnnotationConf

MyBatis 源码分析 - 插件机制

1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 MyBatis 为例,我们可基于 MyBatis 插件机制实现分页.分表,监控等功能.由于插件和业务无关,业务也无法感知插件的存在.因此可以无感植入插件,在无形中增强功能. 开发 MyBatis 插件需要对 MyBatis 比较深了解才行,一般来说最好能够掌握 MyBatis 的源码,门槛相对较高.本篇

mybatis 源码分析(四)一二级缓存分析

本篇博客主要讲了 mybatis 一二级缓存的构成,以及一些容易出错地方的示例分析: 一.mybatis 缓存体系 mybatis 的一二级缓存体系大致如下: 首先当一二级缓存同时开启的时候,首先命中二级缓存: 一级缓存位于 BaseExecutor 中不能关闭,但是可以指定范围 STATEMENT.SESSION: 整个二级缓存虽然经过了很多事务相关的组件,但是最终是落地在 MapperStatement 的 Cache 中(Cache 的具体实例类型可以在 mapper xml 的 cach

Mybatis源码分析之Cache二级缓存原理 (五)

一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(ServiceProvider Interface) ,所有的MyBatis内部的Cache缓存,都应该实现这一接口 Cache的实现类中,Cache有不同的功能,每个功能独立,互不影响,则对于不同的Cache功能,这里使用了装饰者模式实现. 看下cache的实现类,如下图: 1.FIFOCache:先进

MyBatis源码分析-MyBatis初始化流程

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis初始化的过程也就是创建Configura

MyBatis源码分析-SQL语句执行的完整流程

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis框架主要完成的是以下2件事情: 根据JD