mybatis中Configuraion和MappedStatement解析

Configuraion和MappedStatement解析

1、Configuraion组成

首先看下Configuration对象的组成:

2、Configuration作用

2.1、MapperResisty的作用:

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  
}

看下大概的结构如上所示,保存的是mapper接口对应的生产工厂,也就是说通过工厂来产生对应的mapper的代理对象。

MapperProxyFactory对应的方法来产生代理对象

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

mybatis中动态代理的三个地方的使用:

  • 产生mapper接口的代理对象;
  • 懒加载
  • 插件

2.2、Caches

在mapperstatement中保存的就是全局cache缓存

2.3、resultmaps

resultmap是可以出现在每个xml配置文件中的,但是这里直接抽取到configuration这个大对象中来进行保存,说明了resultmap可以使用其他xml配置文件中的resultmap来进行使用的。

2.4、简单工厂模式

可以利用简单工厂模式来创建对应的执行器,获取得到configuration对象来进行创建。

我们设置是可以手动的来修改执行器的类型,比如说执行器:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 没有指定,那么就使用默认的即可
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    // 是否开启二级缓存
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}

通过设置来设计对应的执行器的类型即可。

Configuration的构建

Configuraion的作用

在mybatis进行配置文件的解析的过程中,会将mybatis中的三种配置文件(xml和注解)解析成configuration对象。

1、在全局配置中的属性配置信息都会组装到Configuration对象中来。

2、全局组件解析

2.1、MapperResisty的作用

MapperRegistry类的作用就是将mapper接口进行存放,保存对应的可以创建动态代理的工厂。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  
}

看下大概的结构如上所示,保存的是mapper接口对应的生产工厂,也就是说通过工厂来产生对应的mapper的代理对象。

MapperProxyFactory对应的方法来产生代理对象

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

2.1、ResultMaps

ResultMap是位于每个xml文件中的,但是这里却不是私有的,但是这里将它保存到全局配置对象中,为的是其他的xml中的mappedstatent也能够来进行引用

在解析的时候,会由接口出发xml的解析,解析xml的时候,也会出发接口的解析。

注解的使用这里不需要来进行解释,直接查看对应的xml的解析过程即可。

因为在configuration的组装过程中,呈现出来的是一个树状结构,那么只需要按照这个树状结构来进行解析即可。不需要来做一些其他的复杂的操作过程。

下面根据代码来进行解析:

    @Test
    public void test() throws IOException {
        String resource = "sqlmapconfig.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 解析配置文件并产生SqlSessionFactory对象,利用其产生sqlsession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.queryUserById(12);
        System.out.println(user);
    }

因为我们是根据xml来进行设置的,所以将会来到SqlSessionFactoryBuilder中的build方法中来:

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {      
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 构建文档解析树来进行解析
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        // 这里可以发现传入进来的流自己也会来进行关闭掉
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

看下这里的解析过程:

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析对应的结点并返回最终的configuration对象
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

可以看下接下configuration这个结点的详细解析过程:

  private void parseConfiguration(XNode root) {
    try {
      // 这里就说明了config文件中的顺序性
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(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"));
      // 重点解析过程,解析mapper的过程
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

首先来看下插件的设置过程:

  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        // 发现这里无非也是反射利用无参构造来创建一个对象并设置到插件中来
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        // 添加到configuration中来
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

解析mapper映射文件

下面来看下解析mapper接口的过程:

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 从这里也可以看到mapper结点的四种写法:package、resource、url和class
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // 这里直接添加到configuration中来
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            try(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);
            try(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.");
          }
        }
      }
    }
  }

我们最常使用的也是利用package来进行使用的。来到configuration对象中来:

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);  
public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

利用mapperRegistry来添加对应的包名下的所有的interface,看下mapperRegistry对应的结构:

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
}  

注册mapper的接口,对应的key为mapper接口的类型,value是生产mapper代理的工厂。

那么继续来看:

  public void addMappers(String packageName) {
    // 根据包名
    addMappers(packageName, Object.class);
  }

  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();
    // 遍历所有的mapper接口
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  public <T> void addMapper(Class<T> type) {
    // 接口才可以
    if (type.isInterface()) {
      // MapperRegistry是否已经包含了对应的接口
      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);
        }
      }
    }
  }

在解析注解的时候会来解析对应的xml文件

  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 在这里来加载对应的文件
      loadXmlResource();
      ......
  }

解析对应的xml文件:

  private void loadXmlResource() {
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";     
      // 替换包名类名
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {
        try {
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e2) {
        }
      }
      if (inputStream != null) {
        // 根据xml文件对应的位置来进行解析,这里也就决定了为什么mapper接口需要和xml文件位置固定
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

具体解析过程:

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 查询具体的信息
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
	// 处理完结果映射
    parsePendingResultMaps();
    // 处理完缓存引用
    parsePendingCacheRefs();
    // 处理完mapperstate
    parsePendingStatements();
  }

来到这里的过程:

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // sql引用
      sqlElement(context.evalNodes("/mapper/sql"));
      // 构建Statement
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

解析MappedStatement

首先直接来看对应的流程图即可。

xml解析

注解解析

重点就在于构建select|insert|update|delete对应的statement

  private void buildStatementFromContext(List<XNode> list) {
    // 数据库厂商ID,一般为空
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }   
    buildStatementFromContext(list, null);
  }

继续走:

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // 循环遍历来解析select|insert|update|delete结点
    for (XNode context : list) {
      // 对于每个结点都会来新建一个对象解析
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

看下对应的解析流程:

 public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    // 获取得到结点名称,无非是mapper中的方法名
    String nodeName = context.getNode().getNodeName();
    // 增伤改查
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 是否是select
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 如果是select,如果xml中没有设置,这里将会为fasle
    // 如果是增删改,那么默认是true。默认的原因所在
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 查询默认使用二级缓存;增伤改默认不适用
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());
    // 参数类型,也就是说看看是否有别名
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // 解析生成之后的key使用什么方式来进行生成,如果没有配置,那么默认使用数据库中的
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    // 组装成sqlsource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // 默认是预处理器类型
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    // 每次从数据库获取得到多少
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // 超时时间
    Integer timeout = context.getIntAttribute("timeout");
    // 淘汰
    String parameterMap = context.getStringAttribute("parameterMap");
    // 返回的结果类型
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    // 引用的是哪个
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");
    // 通过构建小助手,添加到mappedstatement中来
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

通过这个方法直接来进行构建:

  public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
	// 无非就是将上面的重新设置了一遍。所以又需要可以自己来设置对应的MappedStatement
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    // 构建完成,放入到configuration对象中来,可以的key是ms.getId()
    // 后续的使用中,也可以从这种来进行获取得到对应的mappedstatement
    configuration.addMappedStatement(statement);    
    return statement;
  }

添加完成之后整个configuration就已经解析完成了。

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

构建起来configuration,那么通过configuration也就可以获取得到所有的信息。

posted @ 2022-06-01 11:24  雩娄的木子  阅读(914)  评论(0编辑  收藏  举报