MyBatis-源码分析

简介: MyBatis-源码分析

一、MyBatis源码分析


1.三层划分介绍


接下来我们就开始MyBatis的源码之旅,首先大家要从宏观上了解Mybatis的整体框架分为三层,分别是基础支持层、核心处理层、和接口层。如下图

image.png


然后根据前面讲解的MyBatis的应用案例,给出MyBatis的主要工作流程图

image.png

在MyBatis的主要工作流程里面,不同的功能是由很多不同的类协作完成的,它们分布在MyBatis jar包的不同的package里面。


image.png

大概有一千多个类,这样看起来不够清楚,不知道什么类在什么环节工作,属于什么层次。MyBatis按照功能职责的不同,所有的package可以分成不同的工作层次。上面的那个图已经给大家展现了


1.1 接口层


首先接口层是我们打交道最多的。核心对象是SqlSession,它是上层应用和MyBatis打交道的桥梁,SqlSession上定义了非常多的对数据库的操作方法。接口层在接收到调用请求的时候,会调用核心处理层的相应模块来完成具体的数据库操作。
复制代码


1.2 核心处理层


接下来是核心处理层。既然叫核心处理层,也就是跟数据库操作相关的动作都是在这一层完成的。


核心处理层主要做了这几件事:



  1. 把接口中传入的参数解析并且映射成JDBC类型;


  1. 解析xml文件中的SQL语句,包括插入参数,和动态SQL的生成;


  1. 执行SQL语句;


  1. 处理结果集,并映射成Java对象。


插件也属于核心层,这是由它的工作方式和拦截的对象决定的。



1.3 基础支持层


最后一个就是基础支持层。基础支持层主要是一些抽取出来的通用的功能(实现复用),用来支持核心处理层的功能。比如数据源、缓存、日志、xml解析、反射、IO、事务等等这些功能,
复制代码



2. 核心流程


分析源码我们还是从编程式的Demo入手。Spring的集成后面会介绍
复制代码


/**
     * MyBatis getMapper 方法的使用
     */
    @Test
    public void test2() throws Exception{
        // 1.获取配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        // 2.加载解析配置文件并获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        // 3.根据SqlSessionFactory对象获取SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 4.通过SqlSession中提供的 API方法来操作数据库
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = mapper.selectUserList();
        for (User user : list) {
            System.out.println(user);
        }
        // 5.关闭会话
        sqlSession.close();
    }
复制代码


上面我们通过一个比较复杂的步骤实现了MyBatis的数据库查询操作。下面我们会按照这5个步骤来分析MyBatis的运行原理
复制代码


看源码的注意事项


  1. 一定要带着问题去看,猜想验证。


  1. 不要只记忆流程,学编程风格,设计思想(他的代码为什么这么写?如果不这么写呢?包括接口的定义,类的职责,涉及模式的应用,高级语法等等)。


  1. 先抓重点,就像开车熟路,哪个地方限速,哪个地方变道,要走很多次。先走主干道,再去、覆盖分支小路。


  1. 记录核心流程和对象,总结层次、结构、关系,输出(图片或者待注释的源码)。
  2. 培养看源码的信心和感觉,从带着看到自己去看,看更多的源码。


  1. debug还是直接Ctrl+Alt+B跟方法?debug可以看到实际的值,比如到底是哪个实现类,value到底是什么。但是Ctrl+Alt+B不一定能走到真正的对象,比如有代理或者父类方法,或者多个实现的时候。熟悉流程之后,直接跟方法。



2.1 核心对象的生命周期



2.1.1 SqlSessionFactoryBuiler


首先是SqlSessionFactoryBuiler。它是用来构建SqlSessionFactory的,而SqlSessionFactory只需要一个,所以只要构建了这一个SqlSessionFactory,它的使命就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。
复制代码


2.1.2 SqlSessionFactory


SqlSessionFactory是用来创建SqlSession的,每次应用程序访问数据库,都需要创建一个会话。因为我们一直有创建会话的需要,所以SqlSessionFactory应该存在于应用的整个生命周期中(作用域是应用作用域)。创建SqlSession只需要一个实例来做这件事就行了,否则会产生很多的混乱,和浪费资源。所以我们要采用单例模式。
复制代码


2.1.3 SqlSession


SqlSession是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在请求开始的时候创建一个SqlSession对象,在请求结束或者说方法执行完毕的时候要及时关闭它(一次请求或者操作中)。
复制代码


2.1.4 Mapper


Mapper(实际上是一个代理对象)是从SqlSession中获取的。
复制代码


UserMapper mapper = sqlSession.getMapper(UserMapper.class);
复制代码


它的作用是发送SQL来操作数据库的数据。它应该在一个SqlSession事务方法之内。


image.png

2.2 SqlSessionFactory


首先我们来看下SqlSessionFactory对象的获取
复制代码


SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
复制代码



2.2.1 SqlSessionFactoryBuilder


首先我们new了一个SqlSessionFactoryBuilder,这是建造者模式的运用(建造者模式用来创建复杂对象,而不需要关注内部细节,是一种封装的体现)。MyBatis中很多地方用到了建造者模式(名字以Builder结尾的类还有9个)。
复制代码


SqlSessionFactoryBuilder中用来创建SqlSessionFactory对象的方法是build(),build()方法有9个重载,可以用不同的方式来创建SqlSessionFactory对象。SqlSessionFactory对象默认是单例的。
复制代码


public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >>
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 解析XML,最终返回一个 DefaultSqlSessionFactory >>
      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.
      }
    }
  }
复制代码


在build方法中首先是创建了一个XMLConfigBuilder对象,XMLConfigBuilder是抽象类BaseBuilder的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类(关联到源码路径),比如:
复制代码


  • XMLMapperBuilder:解析Mapper映射器


  • XMLStatementBuilder:解析增删改查标签


  • XMLScriptBuilder:解析动态SQL


然后是执行了
复制代码


build(parser.parse());
复制代码


构建的代码,parser.parse()方法返回的是一个Configuration对象,build方法的如下


public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
复制代码


在这儿我们可以看到SessionFactory最终实现是DefaultSqlSessionFactory对象。


2.2.2 XMLConfigBuilder


然后我们再来看下XMLConfigBuilder初始化的时候做了哪些操作
复制代码


public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    // EntityResolver的实现类是XMLMapperEntityResolver 来完成配置文件的校验,根据对应的DTD文件来实现
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
复制代码


再去进入重载的构造方法中


private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration()); // 完成了Configuration的初始化
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props); // 设置对应的Properties属性
    this.parsed = false; // 设置 是否解析的标志为 false
    this.environment = environment; // 初始化environment
    this.parser = parser; // 初始化 解析器
  }
复制代码


2.2.3 Configuration



然后我们可以看下Configuration初始化做了什么操作


image.png

完成了类型别名的注册工作,通过上面的分析我们可以看到XMLConfigBuilder完成了XML文件的解析对应XPathParser和Configuration对象的初始化操作,然后我们再来看下parse方法到底是如何解析配置文件的


2.2.4 parse解析


parser.parse()
复制代码


进入具体的解析方法


public Configuration parse() {
     // 检查是否已经解析过了
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // XPathParser,dom 和 SAX 都有用到 >>  开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
复制代码


parseConfiguration方法


private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 对于全局配置文件各种标签的解析
      propertiesElement(root.evalNode("properties"));
      // 解析 settings 标签
      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"));
      // settings 子标签赋值,默认值就是在这里提供的 >>
      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);
    }
  }
复制代码


2.2.4.1 全局配置文件解析



properties解析


private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      // 创建了一个 Properties 对象,后面可以用到
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        // url 和 resource 不能同时存在
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      // 加载resource或者url属性中指定的 properties 文件
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        // 和 Configuration中的 variables 属性合并
        defaults.putAll(vars);
      }
      // 更新对应的属性信息
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }
复制代码

第一个是解析&#x3c;properties>标签,读取我们引入的外部配置文件,例如db.properties。


这里面又有两种类型,一种是放在re

source目录下的,是相对路径,一种是写的绝对路径的(url)。


解析的最终结果就是我们会把所有的配置信息放到名为defaults的Properties对象里面(Hashtable对象,KV存储),最后把XPathParser和Configuration的Properties属性都设置成我们填充后的Properties对象。


settings解析


private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    // 获取settings节点下的所有的子节点
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      // 
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }
复制代码


getChildrenAsProperties方法就是具体的解析了


public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
      // 获取对应的name和value属性
      String name = child.getStringAttribute("name");
      String value = child.getStringAttribute("value");
      if (name != null && value != null) {
        properties.setProperty(name, value);
      }
    }
    return properties;
  }
复制代码


loadCustomVfs(settings)方法


loadCustomVfs是获取Vitual File System的自定义实现类,比如要读取本地文件,或者FTP远程文件的时候,就可以用到自定义的VFS类。
复制代码


根据&#x3c;settings>标签里面的&#x3c;vfsImpl>标签,生成了一个抽象类VFS的子类,在MyBatis中有JBoss6VFS和DefaultVFS两个实现,在io包中。


private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    String value = props.getProperty("vfsImpl");
    if (value != null) {
      String[] clazzes = value.split(",");
      for (String clazz : clazzes) {
        if (!clazz.isEmpty()) {
          @SuppressWarnings("unchecked")
          Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
          configuration.setVfsImpl(vfsImpl);
        }
      }
    }
  }
复制代码


最后赋值到Configuration中。
复制代码



loadCustomLogImpl(settings)方法


loadCustomLogImpl是根据
复制代码


&#x3c;logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括LOG4J,LOG4J2,SLF4J等等,在logging包中。


private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
  }
复制代码



typeAliases解析


这一步是类型别名的解析
复制代码


private void typeAliasesElement(XNode parent) {
    // 放入 TypeAliasRegistry
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              // 扫描 @Alias 注解使用
              typeAliasRegistry.registerAlias(clazz);
            } else {
              // 直接注册
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }
复制代码



plugins解析

插件标签的解析
复制代码


private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 获取<plugin> 节点的 interceptor 属性的值
        String interceptor = child.getStringAttribute("interceptor");
        // 获取<plugin> 下的所有的properties子节点
        Properties properties = child.getChildrenAsProperties();
        // 获取 Interceptor 对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        // 设置 interceptor的 属性
        interceptorInstance.setProperties(properties);
        // Configuration中记录 Interceptor
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
复制代码


插件的具体使用后面专门介绍

objectFactory,objectWrapperFactory及reflectorFactory解析


private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
      // 获取<objectFactory> 节点的 type 属性
      String type = context.getStringAttribute("type");
      // 获取 <objectFactory> 节点下的配置信息
      Properties properties = context.getChildrenAsProperties();
      // 获取ObjectFactory 对象的对象 通过反射方式
      ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      // ObjectFactory 和 对应的属性信息关联
      factory.setProperties(properties);
      // 将创建的ObjectFactory对象绑定到Configuration中
      configuration.setObjectFactory(factory);
    }
  }
  private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      configuration.setObjectWrapperFactory(factory);
    }
  }
  private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      configuration.setReflectorFactory(factory);
    }
  }
复制代码



ObjectFactory用来创建返回的对象。


ObjectWrapperFactory用来对对象做特殊的处理。比如:select没有写别名,查询返回的是一个Map,可以在自定义的objectWrapperFactory中把下划线命名变成驼峰命名。

ReflectorFactory是反射的工具箱,对反射的操作进行了封装(官网和文档没有这个对象的描述)。


以上四个对象,都是用resolveClass创建的。



settingsElement(settings)方法

这里就是对&#x3c;settings>标签里面所有子标签的处理了,前面我们已经把子标签全部转换成了Properties对象,所以在这里处理Properties对象就可以了。


settings二级标签中一共26个配置,比如二级缓存、延迟加载、默认执行器类型等等。

需要注意的是,我们之前提到的所有的默认值,都是在这里赋值的。如果说后面我们不知道这个属性的值是什么,也可以到这一步来确认一下。


所有的值,都会赋值到Configuration的属性里面去。


private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }
复制代码



environments解析


这一步是解析&#x3c;environments>标签。


我们前面讲过,一个environment就是对应一个数据源,所以在这里我们会根据配置的&#x3c;transactionManager>创建一个事务工厂,根据&#x3c;dataSource>标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到Configuration里面。


private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          // 事务工厂
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 数据源工厂(例如 DruidDataSourceFactory )
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          // 数据源
          DataSource dataSource = dsFactory.getDataSource();
          // 包含了 事务工厂和数据源的 Environment
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 放入 Configuration
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }
复制代码



databaseIdProviderElement()

解析databaseIdProvider标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。


typeHandlerElement()

跟TypeAlias一样,TypeHandler有两种配置方式,一种是单独配置一个类,一种是指定一个package。最后我们得到的是JavaType和JdbcType,以及用来做相互映射的TypeHandler之间的映射关系,存放在TypeHandlerRegistry对象里面。



private void typeHandlerElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }
复制代码


mapper解析

最后就是&#x3c;mappers>标签的解析。


根据全局配置文件中不同的注册方式,用不同的方式扫描,但最终都是做了两件事情,对于语句的注册和接口的注册。

扫描类型 含义
resource 相对路径
url 绝对路径
package
class 单个接口



private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 不同的定义方式的扫描,最终都是调用 addMapper()方法(添加到 MapperRegistry)。这个方法和 getMapper() 对应
        // package  包
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          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) {
            // resource 相对路径
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析 Mapper.xml,总体上做了两件事情 >>
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            // url  绝对路径
            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) {
            // class  单个接口
            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.");
          }
        }
      }
    }
  }
复制代码


然后开始进入具体的配置文件的解析操作


2.2.4.2 映射文件的解析


首先进入parse方法
复制代码


public void parse() {
    // 总体上做了两件事情,对于语句的注册和接口的注册
    if (!configuration.isResourceLoaded(resource)) {
      // 1、具体增删改查标签的解析。
      // 一个标签一个MappedStatement。 >>
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。
      // 一个namespace 一个 MapperProxyFactory >>
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
复制代码


configurationElement()——解析所有的子标签,最终获得MappedStatement对象。


bindMapperForNamespace()——把namespace(接口类型)和工厂类MapperProxyFactory绑定起来。


configurationElement方法


configurationElement是对Mapper.xml中所有具体的标签的解析,包括namespace、cache、parameterMap、resultMap、sql和select|insert|update|delete。
复制代码


private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 添加缓存对象
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析 cache 属性,添加缓存对象
      cacheElement(context.evalNode("cache"));
      // 创建 ParameterMapping 对象
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 创建 List<ResultMapping>
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析可以复用的SQL
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析增删改查标签,得到 MappedStatement >>
      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);
    }
  }
复制代码


在buildStatementFromContext()方法中,创建了用来解析增删改查标签的XMLStatementBuilder,并且把创建的MappedStatement添加到mappedStatements中。


MappedStatement statement = statementBuilder.build();
   // 最关键的一步,在 Configuration 添加了 MappedStatement >>
   configuration.addMappedStatement(statement);
复制代码


bindMapperForNamespace方法


private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // 根据名称空间加载对应的接口类型
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          // 添加到 MapperRegistry,本质是一个 map,里面也有 Configuration >>
          configuration.addMapper(boundType);
        }
      }
    }
  }
复制代码


通过源码分析发现主要是是调用了addMapper()。addMapper()方法中,把接口类型注册到MapperRegistry中:实际上是为接口创建一个对应的MapperProxyFactory(用于为这个type提供工厂类,创建MapperProxy)。
复制代码


public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) { // 检测 type 是否为接口
      if (hasMapper(type)) { // 检测是否已经加装过该接口
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系
        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.
        // 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
复制代码


同样的再进入parse方法中查看


public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 先判断 Mapper.xml 有没有解析,没有的话先解析 Mapper.xml(例如定义 package 方式)
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      // 处理 @CacheNamespace
      parseCache();
      // 处理 @CacheNamespaceRef
      parseCacheRef();
      // 获取所有方法
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            // 解析方法上的注解,添加到 MappedStatement 集合中 >>
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }
复制代码


大家可以继续进入到parseStatement方法中查看。


总结:


  1. 我们主要完成了config配置文件、Mapper文件、Mapper接口中注解的解析。


  1. 我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。


  1. 最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例。

最后的时序图



image.png

2.3 SqlSession


程序每一次操作数据库,都需要创建一个会话,我们用openSession()方法来创建。接下来我们看看SqlSession创建过程中做了哪些操作
复制代码


SqlSession sqlSession = factory.openSession();
复制代码


通过前面创建的DefaultSqlSessionFactory的openSession方法来创建
复制代码


@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
复制代码

首先会获取默认的执行器类型。默认的是simple


继续往下


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      // 获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创建事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 根据事务工厂和默认的执行器类型,创建执行器 >>
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
复制代码


我们在解析environment标签的时候有创建TransactionFactory对象


image.png

根据事务工厂和默认的执行器类型,创建执行器


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 {
      // 默认 SimpleExecutor
      executor = new SimpleExecutor(this, transaction);
    }
    // 二级缓存开关,settings 中的 cacheEnabled 默认是 true
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 植入插件的逻辑,至此,四大对象已经全部拦截完毕
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
复制代码


最后返回的是一个DefaultSqlSession对象


image.png

在这个DefaultSqlSession对象中包括了Configuration和Executor对象


总结:创建会话的过程,我们获得了一个DefaultSqlSession,里面包含了一个Executor,Executor是SQL的实际执行对象。


image.png

2.4 Mapper代理对象


接下来看下通过getMapper方法获取对应的接口的代理对象的实现原理
复制代码


// 4.通过SqlSession中提供的 API方法来操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
复制代码


进入DefaultSqlSession中查看


public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // mapperRegistry中注册的有Mapper的相关信息 在解析映射文件时 调用过addMapper方法
    return mapperRegistry.getMapper(type, sqlSession);
  }
复制代码


进入getMapper方法


/**
   * 获取Mapper接口对应的代理对象
   */
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取Mapper接口对应的 MapperProxyFactory 对象
    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);
    }
  }
复制代码


进入newInstance方法


public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
复制代码


继续


/**
   * 创建实现了 mapperInterface 接口的代理对象
   */
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
复制代码


最终我们在代码中发现代理对象是通过JDK动态代理创建,返回的代理对象。而且里面也传递了一个实现了InvocationHandler接口的触发管理类。


image.png

总结:获得Mapper对象的过程,实质上是获取了一个JDK动态代理对象(类型是$ProxyN)。这个代理类会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。


image.png


相关文章
|
5月前
|
SQL Java 数据库连接
Mybatis源码分析系列之第三篇:Mybatis的操作类型对象
Mybatis源码分析系列之第三篇:Mybatis的操作类型对象
|
6月前
|
SQL XML Java
源码分析系列教程(08) - 手写MyBatis(注解版)
源码分析系列教程(08) - 手写MyBatis(注解版)
44 0
|
4月前
|
SQL Java 数据库连接
MyBatis源码篇:mybatis拦截器源码分析
MyBatis源码篇:mybatis拦截器源码分析
|
4月前
|
缓存 Java 数据库连接
|
4月前
|
SQL Java 数据库连接
|
5月前
|
设计模式 SQL Java
Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解
Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解
|
5月前
|
存储 SQL Java
Mybatis源码分析系列之第二篇:Mybatis的数据存储对象
Mybatis源码分析系列之第二篇:Mybatis的数据存储对象
|
5月前
|
Java 关系型数据库 数据库连接
Mybatis源码分析系列之第一篇:回顾一下MyBatis的使用
Mybatis源码分析系列之第一篇:回顾一下MyBatis的使用
|
9月前
|
Java 数据库连接 mybatis
mybatis采坑记及源码分析
问题描述 有些需求,需要把上一步批量操作返回的主键作为下个表的关联使用,这个时候用mybatis批量操作,mybatis批量操作有些版本不能返回主键,只能一个一个的插入,这样就降低了效率。 接口
44 0
|
10月前
|
SQL XML 缓存
Mybatis源码分析 2:解析XML并映射到Sql
# XMLStatementBuilder:对单个XNode节点进行解析,得到具体的SqlSource并以此生成MappedStatement ## parseStatementNode方法: ```JAVA private final MapperBuilderAssistant builderAssistant; // 记录了当前mapper的namespace等基础信息 private
92 0
Mybatis源码分析 2:解析XML并映射到Sql