Mybatis source code analysis based on annotation Mapper

     At present, Mybatis can configure SQL in the form of annotations in addition to configuring SQL through XML. This article mainly introduces how Mybatis handles annotation SQL mapping, and analyzes the processing process through source code.

 XML placement

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

  parsing process 

 

 

  private void mapperElement(XNode parent) throws Exception {
    //If the mapper node is configured in the configuration
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //If the configuration pair is a package path
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          //Get the resource attribute in the mapper element
          String resource = child.getStringAttribute("resource");
          //Get the url attribute in the mapper element
          String url = child.getStringAttribute("url");
          //Get the class attribute in the mapper element, if the mapper configuration based on annotations is configured with class
          String mapperClass = child.getStringAttribute("class");
          //For resource, url, mapperClass, use resource first, followed by url and finally class
          if (resource != null && url == null && mapperClass == null) {
            //create exception context
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //Create XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // Parse 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) {
            //Get the interface of mapper
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            //Register the interface to a known 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);
  }

  

//Register Mapper
  public <T> void addMapper(Class<T> type) {
    //if type is an interface
    if (type.isInterface()) {
      / / Determine whether the interface has already registered the corresponding Mapper, if so, throw an exception, because the key of knownMappers is type
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //Create a MapperProxyFactory for this interface and keep it registered to knownMappers
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        //Create MapperAnnotationBuilder
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //Parse Annotation
        parser.parse();
        //Successful parsing means the loading is complete
        loadCompleted = true;
      } finally {
        // if the load is not complete remove it from 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();
//Determine whether the resource has been registered
if (!configuration.isResourceLoaded(resource)) {
//If it has not been registered, you need to load the XML resource
loadXmlResource();
/ /Add the resource name to the registered collection
configuration.addLoadedResource(resource);
//Set nameSpace
assistant.setCurrentNamespace(type.getName());
//parse cache
parseCache();
//parse cacheRef
parseCacheRef();
/ /Get
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
//If it is not a bridge method, parse the statement
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
//解析待定方法
parsePendingMethods();
}

  

private void loadXmlResource() {
    // Determine if the resource has been loaded
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      //Get the path of the resource
      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 the resource exists
      if (inputStream != null) {
        //Build XMLMapperBuilder
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        // Parse XML
        xmlParser.parse ();
      }
    }
  }

  

private void parseCache() {
    //Get the @CacheNamespace annotation on the interface
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    //if the annotation exists
    if (cacheDomain != null) {
      //Get the cache size of the annotation configuration
      Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
      //Get the cache refresh frequency
      Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
      //Parse the properties of the CacheNamespace configuration
      Properties props = convertToProperties(cacheDomain.properties());
      //Create a cache with the data configured by the annotation
      assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
    }
  }

  

private void parseCacheRef() {
    //Get the @CacheNamespaceRef annotation on the interface
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
    //If a cache reference is configured
    if (cacheDomainRef != null) {
      //get the reference type
      Class<?> refType = cacheDomainRef.value();
      String refName = cacheDomainRef.name();
      // Throws an exception if the reference type and reference name are both null
      if (refType == void.class && refName.isEmpty()) {
        throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
      }
      //If the reference type and reference name are configured with valid data at the same time, an exception will be thrown, these two are mutually exclusive data
      if (refType != void.class && !refName.isEmpty()) {
        throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
      }
      //get namespace
      String namespace = (refType != void.class) ? refType.getName() : refName;
      // use cache
      assistant.useCacheRef(namespace);
    }
  }

  

  void parseStatement(Method method) {
    //get parameter type
    Class<?> parameterTypeClass = getParameterType(method);
    //Get LanguageDriver
    LanguageDriver languageDriver = getLanguageDriver(method);
    //Get Sqlsource from annotation
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    //if sqlSource is not null
    if (sqlSource != null) {
      //Get the @Options annotation
      Options options = method.getAnnotation(Options.class);
      //create statementId
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      //get sql type
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      //Is it a query
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      //Do you need to refresh the cache, if it is not the query default value is true, the query default value is false
      boolean flushCache = !isSelect;
      //Whether to use the cache, the query defaults to true, not the query defaults to false
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = null;
      //if insert or update
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        //Get the SelectKey annotation
        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 {//If it is not insert or update, that is, query and delete, then keyGenerator is NoKeyGenerator instance
        keyGenerator = NoKeyGenerator.INSTANCE;
      }
      //if the @Options annotation is not null
      if (options != null) {
        //Set whether to refresh the cache according to the configured value
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        //whether to use cache
        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;
      //Get ResultMap annotation
      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 {
      //Get annotations of @Select, @Insert, @Update, @Delete types
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      //Get @SelectProvider, @InsertProvider, @UpdateProvider @DeleteProvider annotations
      Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      //if the SQL annotation is not null
      if (sqlAnnotationType != null) {
        //At the same time, if the sqlProvider annotation is not empty, an exception will be thrown, and the two are mutually exclusive
        if (sqlProviderAnnotationType != null) {
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        //get annotation
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        //Get the value of the annotation configuration
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        //Create SqlSource based on the configured data
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {//If the SqlProvider annotation is not empty
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        //Create a ProviderSqlSource
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
      }
      //If neither Sql annotation nor SqlProvider annotation is configured, return null
      return null;
    } catch (Exception e) {
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }

  

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324760294&siteId=291194637