精通Mybatis之Configuration配置体系

Posted 木兮君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了精通Mybatis之Configuration配置体系相关的知识,希望对你有一定的参考价值。

前言

这次小编要给大家介绍的是Configuration配置体系,不知道大家是怎么学习源码的,如果大家是从xml加载开始学起的话,那Configuration就是第一步了,因为他是将xml解析成Configuration,然后Configuration直接或间接的创建和管理绝大多数组件包括Executor、StatementHandler、Cache、MappedStatement等等。而小编是差不多最后介绍有了前面的基础,Configuration也变得不难了。

Configuration

Configuration 是整个MyBatis的配置体系集中管理中心,前面小编所介绍的Executor、StatementHandler、Cache、MappedStatement等绝大部分组件都是由它直接或间接的创建和管理。此外影响这些组件行为的属性配置也是由它进行保存和维护。如cacheEnabled、lazyLoadingEnabled 等。
对于具体配置可以看mybatis官网的配置。里面非常详尽。

配置概览

这边小编从官网上取出使用比较频繁的配置稍做举例:

这里小编对@Options稍作解释:
@Options作用在接口的方法上,他其实和@Select是搭配使用的,但是分开写也就为什么不在@Select中,第一是我们很少去配置Options,第二分开更加明确写法的容易明白。@Options包含是否使用二级缓存,缓存刷新的策略,使用哪种statementHandler等。

Configuration作用

Configuration 配置来源有三项:

  1. Mybatis-config.xml 启动文件,全局配置、全局组件都是来源于此。
  2. Mapper.xml SQL映射(MappedStatement) \\结果集映射(ResultMapper)都来源于此。
  3. @Annotation SQL映射与结果集映射的另一种表达形式。

总结一下Configuration主要作用如下:

  • 存储全局配置信息,其来源于settings(设置)
  • 初始化并维护全局基础组件
    typeAliases(类型别名)
    typeHandlers(类型处理器)
    plugins(插件)
    environments(环境配置)
    cache(二级缓存空间)
  • 初始化并维护MappedStatement
  • 组件构造器,并基于插件进行增强
    newExecutor(执行器)
    newStatementHandler(JDBC处理器)
    newResultSetHandler(结果集处理器)
    newParameterHandler(参数处理器)

这里小编介绍一下全局组件,因为上面并没有将所有的组件全部列出,说明一些小编认为比较重要的组件

MapperRegistry

这个组件比较重要,sqlsession获取相应的mapper,mapper明明为接口却可以直接调用方法并获取相应的结果,就靠他了。看下源码:

public class MapperRegistry 

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

  public MapperRegistry(Configuration config) 
    this.config = config;
  

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) 
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) 
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    
    try 
      return mapperProxyFactory.newInstance(sqlSession);
     catch (Exception e) 
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    
  

  public <T> boolean hasMapper(Class<T> type) 
    return knownMappers.containsKey(type);
  

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

  /**
   * @since 3.2.2
   */
  public Collection<Class<?>> getMappers() 
    return Collections.unmodifiableCollection(knownMappers.keySet());
  

  /**
   * @since 3.2.2
   */
  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) 
      addMapper(mapperClass);
    
  

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName) 
    addMappers(packageName, Object.class);
  


首先MapperRegistry 是在configuration中创建的,根据上面源码:他在mappers中配置扫描包或class文件会增加Map<Class<?>, MapperProxyFactory<?>> knownMappers中的值,而MapperProxyFactory就是动态代理工厂,其作用就是生产动态代理类。动态代理在mybatis中这是第二次提到了,第一次是在懒加载机制的时候使用动态代理。

InterceptorChain

拦截器链主要是为了做增强使用,主要是对newExecutor(执行器),newStatementHandler(JDBC处理器),
newResultSetHandler(结果集处理器),newParameterHandler(参数处理器)。后续小编讲插件体系的时候会着重说明

MappedStatement

MappedStatement主要承载了mapper.xml或者是mapper.java文件解析后的sql声明,也就是讲前面文件中所有的设置变成MappedStatement的元素。

Configuration组件构建过程

元素承载

无论是xml 还是配置的注解元素最后都要被转换成JAVA配置属性或对象组件来承载。其对应关系如下:

  1. 全配置(config.xml) 由Configuration对像属性承载
  2. sql映射<select|insert…> 或@Select 等由MappedStatement对象承载
  3. 缓存<cache…> 或@CacheNamespace 由Cache对象承载
  4. 结果集映射 由ResultMap 对象承载

上面比较抽象,小编画个结构图大家就明白了:

这边大家有没有奇怪resultMap本来应该属于一个namespace下的为什么要放入公共环境配置中去呢?这是因为resultMap的引用可以跨namespace。

配置文件解析过程

配置文件解析需要我们分开讨论,首先来分析XML解析过程。xml配置解析其底层使用dom4j先解析成一棵节点树,然后根据不同的节点类型与去匹配不同的解析器。最终解析成特定组件。

解析器的基类是BaseBuilder 其内部包含全局的configuration 对象,这么做的用意是所有要解析的组件最后都要集中归属至configuration。接下来了解一下每个解析器的作用:

  • XMLConfigBuilder :解析config.xml文件,会直接创建一个configuration对象,用于解析全局配置 。
  • XMLMapperBuilder :解析Mapper.xml文件,内容包含 等
  • MapperBuilderAssistant:Mapper.xml解析辅助,在一个Mapper.xml中Cache是对Statement(sql声明)共享的,共享组件的分配即由该解析实现。
  • XMLStatementBuilder:SQL映射解析 即<select|update|insert|delete> 元素解析成MapperStatement。
  • SqlSourceBuilder:Sql数据源解析,将声明的SQL解析可执行的SQL。
  • XMLScriptBuilder:解析动态SQL数据源当中所设置 SqlNode脚本集。

当然我们测试的时候使用的是SqlSessionFactoryBuilder的build方法,但是他其实什么都不做直接调用的是XMLConfigBuilder.parse的方法,所以上面小编跳过了SqlSessionFactoryBuilder,然后调用晚回来也直接返回一个new DefaultSqlSessionFactory将configuration放进去就好了。

解析mybatis-config.xml的时序图

流程说明:

  1. 【XmlConfigBuilder】 接收一个config.xml 输入流,然后创建一个空Configuration对象
  2. 【XmlConfigBuilder】解析全局配置
  3. 【XmlConfigBuilder】mapperElements解析,通过Resource或url 指定mapper.xml文件
    3.1【MapperBuilderAssistant】设置缓存并添加至Configuration
    3.2【XMLScriptBuilder】解析生成SQL数据源,包括动态脚本
    3.3【XmlMapperBuilder】解析缓存、结果集配置等公共配置
    3.4【XmlMapperBuilder】解析Sql映射<select|insert|upate|delete>
    3.5【XmlMapperBuilder】构建Statement

解析mapper.java(接口)的时序图


注解解析底层实现是通过反射获取Mapper接口当中注解元素实现。有两种方式一种是直接指定接口名,一种是指定包名然后自动扫描包下所有的接口类。这些逻辑均由Mapper注册器(MapperRegistry)实现。其接收一个接口类参数,并基于该参数创建针对该接口的动态代理工厂,然后解析内部方法注解生成每个MapperStatement 最后添加至Configuration 完成解析。

注意点:当扫描包下的时候,虽然是去扫描mapper.java文件但是他会去扫描相同包下的同一命名的xml,即解析UserMapper.java的时候同时会去解析UserMapper.xml,返回来也是如此。解析xml的时候同样去解析java文件

为什么要加上一个MapperBuilderAssistant?
小编这边是这么思考的。其实无论是xml还是接口类解析,都一开始设置了namespace,这样在添加到configuration的时候就知道讲statementMapper添加到那个命名空间去,否则需要在XMLStatementBuilder和MapperAnnotationBuilder中记录。

总结

今天讲解的configuration体系其实不难,无非就是解析xml或配置文件生成configuration,里面比较重要的点就是mappers配置,mapper.xml或mapper.java怎么变成configuration中的MapperedStatement,还有动态代理类前期是怎么创建进去的。这边对拦截器链没做详细讲解,这个也是mybatis的扩展点,包括分页什么的,小编后续会介绍的,今天的configuration就告一段落了,再接再厉加油!

以上是关于精通Mybatis之Configuration配置体系的主要内容,如果未能解决你的问题,请参考以下文章

精通Mybatis之插件体系(与中间件实现的一些思考)

精通Mybatis之插件体系(与中间件实现的一些思考)

mybatis源码-解析配置文件之配置文件Configuration解析(超详细, 值得收藏)

Mybatis之Configuration初始化(配置文件.xml的解析)

mybatis源码-解析配置文件之配置文件Mapper解析

mybatis源码解析之Configuration加载