mybatis基于注解mapper源码分析

wei_zw      2022-02-11     617

关键词:

     目前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> 

 

 @Insert("insert into table2 (name) values(#{name})")
    @SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
    int insertTable2(Name name);
    
    @Insert("insert into table2 (name) values(#{name})")
    @Options(useGeneratedKeys=true, keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED")
    int insertTable2WithGeneratedKey(Name name);

  

 解析过程 

 

 

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

  

如果存在XML配置也是会加载的  

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

  下图是同时存在XML和annotation的情况

 

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

  

 

mybatis注解@select@update分析(代码片段)

MyBatis注解@Select、@Update分析前面几篇文章分别分析了Mybatis中的Configuration的配置信息,MyBatis中的Mapper调用等等,在分析配置信息时只是讲了如何解析xml中的sql查询,但是并没有讲怎么解析Mapper中注解对应的SQL... 查看详情

mybatis中的@mapper注解及配套注解使用详解(上)(代码片段)

前言:从mybatis3.4.0开始加入了@Mapper注解,目的就是为了不再写mapper映射文件(那个xml写的是真的蛋疼。。。)。很恶心的一个事实是源码中并没有对于这个注解的详细解释现在我们通过一个简易的maven项目去了解@Mapper注解的使... 查看详情

@mapperscan与@mapper(代码片段)

...apper背景说明@MapperScan与@Mapper的作用通过@Mapper让Mybatis对接口提供代理实现通过@MapperScan让Mybatis对接口提供代理实现背景说明我们在编写mapper时,只需要编写接口而不需要对其实现,由Mybatis框架对接口提供对... 查看详情

mybatis源码分析

说明:以下分析基于spring-framework-5.0.x,mybatis-spring-1.3.2,mybatis-3.4.6相关源码可自行去github下载或者maven依赖然后利用类似ideal工具自动关联源码功能。我们知道spring对bean的管理,我们可以通过多种方式将bean添加进spring中进行管理其... 查看详情

mybatis设计与源码分析

Mybatis设计分析一_踩踩踩从踩的博客-CSDN博客 前言前面文章主要针对mybatis有个大体的设计,包括对整个框架包括对于mapper的存储,以及如何应对我们常见的增删改查如何去定义好注解或者xml的方式来定义,如何进行... 查看详情

springboot整合mybatis基于注解开发以及动态sql的使用

  让我们回忆一下上篇博客中mybatis是怎样发挥它的作用的,主要是三类文件,第一mapper接口,第二xml文件,第三全局配置文件(application.properties),而今天我们就是来简化mybatis的工作的——利用注解替代xml配置文件。  先... 查看详情

mybatis设计与源码分析(代码片段)

Mybatis设计分析一_踩踩踩从踩的博客-CSDN博客 前言前面文章主要针对mybatis有个大体的设计,包括对整个框架包括对于mapper的存储,以及如何应对我们常见的增删改查如何去定义好注解或者xml的方式来定义,如何进行... 查看详情

mybatis的mapper为什么不用写实现类的源码分析(代码片段)

  首先看下我们spring和mybatis扫描包的时候是怎样写:有两种写法:  第一种写法:<mybatis-spring:scanbase-package="com.gupaoedu.crud.dao"/>     第二种写法:<beanid="mapperScanner"class="org.mybatis.spring.mapper.Mapp 查看详情

mybatis源码分析之@selectprovider注解使用详解(代码片段)

MyBatis源码分析之@SelectProvider注解使用详解之前讲了MyBatis的配置、plugin、Select查询,还有@MapKey注解的使用与原理,还有返回@ResultMap等等,我原想直接从MyBatis的缓存开始说起,但是想想还是得说一下MyBatis... 查看详情

mybatis源码分析之@resultmap注解详解(代码片段)

MyBatis源码分析之@ResultMap注解详解在前一篇文章讲**@MapKey注解时,我原想将@ResultMap注解也一起拿出来说一下,但是发现@ResultMap解析加载源码非常多,想想就不在一篇文章中讲了,分开单独来说,这... 查看详情

mybatis源码:mybatis配置解析(代码片段)

  Mybatis有两个核心配置,全局配置会影响Mybatis的执行;Mapper配置定义了查询的SQL,下面我们来看看Mybatis是如何加载配置文件的。  本文基于Mybatis源码(一):源码编译准备中案例进行分析,主要示例代码如下:publicstaticvoidma... 查看详情

mybatis源码分析1-如何获取mapper实现类

...看出,id为sqlSessionFactory的bean默认是DefaultSqlSessionFactory。Mybatis的官方推荐,SqlSessionFactory最好是做成单例的,不需要频繁创建。通过api获取session的方式那么我们就看DefaultSqlSessionFactory.openSession方法,如下再看DefaultSqlSession.getMapper... 查看详情

mybatis源码阅读之--整体执行流程

Mybatis执行流程分析Mybatis执行SQL语句可以使用两种方式:使用SqlSession执行update/delete/insert/select操作使用SqlSession获得对应的Mapper,然后调用mapper的相应方法执行语句其中第二种方式获取Mapper的流程在前面已经解析过,请查看文章My... 查看详情

mybatis源码配置文件解析之五:解析mappers标签(解析xml映射文件)

在上篇文章中分析了mybatis解析<mappers>标签,《mybatis源码配置文件解析之五:解析mappers标签 》重点分析了如何解析<mappers>标签中的<package>子标签的过程。mybatis解析<mappers>标签主要完成了两个操作,第一个... 查看详情

mybatismapper接口是如何找到实现类的-源码分析

参考技术AKeyWords:Mybatis原理,源码,MybatisMapper接口实现类,代理模式,动态代理,Java动态代理,Proxy.newProxyInstance,Mapper映射,Mapper实现MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几... 查看详情

mybatis基础篇-mapper代理开发方式(注解方式)

...式采用XML进行配置,假如你不喜欢在项目中见到XML文件,MyBatis也为我们提供了注解开发方式。个人认为,Mapper信息绝大部分情况都应该在XML文件中进行配置,原因是如果采用注解方式,当配置发生变化,就要对注解所在的类重... 查看详情

mybatis的几个重要概念和工作流程

MyBatis几个重要的概念Mapper配置: Mapper配置可以使用基于XML的Mapper配置文件来实现,也可以使用基于Java注解的MyBatis注解来实现,甚至可以直接使用MyBatis提供的API来实现。            ... 查看详情

springbootspringboot集成mybatis中@mapper/@mapperscan和@repository的区别(代码片段)

结论@Mapper一定要有,否则Mybatis找不到mapper。@Repository可有可无,可以消去依赖注入的报错信息。@MapperScan可以替代@Mapper。@Component和@Repository效果都是一样的,只是为了声明为bean1.@Mapper@Mapper是M... 查看详情