mybatis源码的分析

mybatis源码仓库地址

mybatis文档

整体架构
mybatis源码的分析_第1张图片

(1)解析器模块--两个功能

  • 一个功能,是对XPath进行封装,为MyBatis初始化时解析mybatis-config.xml配置文件以及映射配置文件提供支持。
  • 另一个功能,是为处理动态SQL语句中的占位符提供支持。
  • org.apache.ibatis.parsing.XPathParser

解析占位符:


  
  
  
  

eval 元素的方法,用于获得 Boolean、Short、Integer、Long、Float、Double、String 类型的元素的值。

org.apache.ibatis.builder.xml.XMLMapperEntityResolver实现 EntityResolver 接口,MyBatis 自定义 EntityResolver 实现类,用于加载本地的 mybatis-3-config.dtd 和 mybatis-3-mapper.dtd这两个 DTD 文件。

(2)反射模块

org.apache.ibatis.reflection.Reflector,反射器,每个 Reflector 对应一个类。Reflector 会缓存反射操作需要的类的信息,例如:构造方法、属性名、setting / getting 方法等等。

(3)异常模块

org.apache.ibatis.exceptions.IbatisException ,实现 RuntimeException 类,IBatis 的异常基类,已经被废弃

org.apache.ibatis.exceptions.PersistenceException ,继承 IbatisException 类,目前 MyBatis 真正的异常基类

org.apache.ibatis.parsing.ParsingException ,继承 PersistenceException 类,解析异常。org.apache.ibatis.exceptions.TooManyResultsException ,继承 PersistenceException 类,查询返回过多结果的异常。期望返回一条,实际返回了多条。

(4)数据源模块

数据源是实际开发中常用的组件之一。现在开源的数据源都提供了比较丰富的功能,例如,连接池功能、检测连接状态等,选择性能优秀的数据源组件对于提升 ORM 框架乃至整个应用的性能都是非常重要的。

MyBatis 自身提供了相应的数据源实现,当然 MyBatis 也提供了与第三方数据源集成的接口,这些功能都位于数据源模块之中

org.apache.ibatis.datasource.DataSourceFactory

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory ,实现 DataSourceFactory 接口,非池化的 DataSourceFactory 实现类。

UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。 不同的数据库在性能方面的表现也是不一样的,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

  • driver – 这是 JDBC 驱动的 Java 类的完全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
  • url – 这是数据库的 JDBC URL 地址。
  • username – 登录数据库的用户名。
  • password – 登录数据库的密码。
  • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。

org.apache.ibatis.datasource.pooled.PooledDataSourceFactory,

继承了UnpooledDataSourceFactory 

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections – 在任意时间可以存在的活动(也就是正在使用)连接数量,默认值:10
  • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
  • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
  • poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败),默认值:20000 毫秒(即 20 秒)。
  • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程. 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。 默认值:3 (新增于 3.4.5)
  • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动失败时带有一个恰当的错误消息。
  • poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
  • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。

org.apache.ibatis.datasource.jndi.JndiDataSourceFactory ,实现 DataSourceFactory 接口,基于 JNDI 的 DataSourceFactory 实现类

JNDI – 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。这种数据源配置只需要两个属性:

  • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找。
  • data_source – 这是引用数据源实例位置的上下文的路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给初始上下文。比如:

  • env.encoding=UTF8

javax.sql.DataSource 是个神奇的接口,在其上可以衍生出数据连接池、分库分表、读写分离等等功能。

org.apache.ibatis.datasource.unpooled.UnpooledDataSource

org.apache.ibatis.datasource.pooled.PooledDataSource ,实现 DataSource 接口,池化的 DataSource 实现类。

popConnection(String username, String password) 方法,获取 PooledConnection 对象。

pushConnection(PooledConnection conn) 方法,将使用完的连接,添加回连接池中。

pingConnection(PooledConnection conn) 方法,通过向数据库发起 poolPingQuery 语句来发起“ping”操作,以判断数据库连接是否有效。

 

(5)org.apache.ibatis.transaction.Transaction ,事务接口。

org.apache.ibatis.transaction.jdbc.JdbcTransaction ,实现 Transaction 接口,基于 JDBC 的事务实现类。

org.apache.ibatis.transaction.managed.ManagedTransaction ,实现 Transaction 接口,基于容器管理的事务实现类。

org.apache.ibatis.transaction.TransactionFactory ,Transaction 工厂接口。

org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory ,实现 TransactionFactory 接口,JdbcTransaction 工厂实现类。

org.apache.ibatis.transaction.managed.ManagedTransactionFactory ,实现 TransactionFactory 接口,ManagedTransaction 工厂实现类。

(6)缓存模块

org.apache.ibatis.cache.Cache,缓存容器接口。注意,它是一个容器,有点类似HashMap,可以往其中添加各种缓存org.apache.ibatis.cache.impl.PerpetualCache,实现 Cache 接口,永不过期的 Cache 实现类,基于 HashMap 实现类。org.apache.ibatis.cache.decorators.LoggingCache ,实现 Cache 接口,支持打印日志的 Cache 实现类。org.apache.ibatis.cache.decoratorsBlockingCache ,实现 Cache 接口,阻塞的 Cache 实现类。

(7)类型模块

 org.apache.ibatis.type.TypeHandler ,类型转换处理器。

setParameter(...) 方法,是 Java Type => JDBC Type 的过程。

#getResult(...) 方法,是 JDBC Type => Java Type 的过程。

-------------------------------------------------------------------------------------------------

org.apache.ibatis.type.BaseTypeHandler ,实现 TypeHandler 接口,继承 TypeReference 抽象类,TypeHandler 基础抽象类。

TypeHandler 有非常多的子类,当然所有子类都是继承自 BaseTypeHandler 抽象类

(8)IO 模块

资源加载模块,主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能 。

org.apache.ibatis.io.ClassLoaderWrapper ,ClassLoader 包装器。可使用多个 ClassLoader 加载对应的资源,直到有一成功后返回资源。

getResourceAsStream(String resource, ...) 方法,获得指定资源的 InputStream 对象。

org.apache.ibatis.io.Resources ,Resource 工具类。

etResourceAsStream(String resource) 静态方法,获得指定资源的 InputStream 。

getResourceAsReader(String resource) 静态方法,获得指定资源的 Reader 。

(8) 日志模块

 Log4j、 Log4j2、Slf4j 等。

(9)注解模块

demo:

(10)Binding 模块

在调用 SqlSession 相应方法执行数据库操作时,需要指定映射文件中定义的 SQL 节点,如果出现拼写错误,我们只能在运行时才能发现相应的异常。为了尽早发现这种错误,MyBatis 通过 Binding 模块,将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。

值得读者注意的是,开发人员无须编写自定义 Mapper 接口的实现,MyBatis 会自动为其创建动态代理对象。在有些场景中,自定义 Mapper 接口可以完全代替映射配置文件,但有的映射规则和 SQL 语句的定义还是写在映射配置文件中比较方便,例如动态 SQL 语句的定义。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

mybatis初始化

MyBatis 的初始化流程的入口是 SqlSessionFactoryBuilder 

1加载mybatis.conf

 InputStream stream = Resources.getResourceAsStream("");
 SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
public class SqlSessionFactoryBuilder {
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
}

BaseBuilder 是 XMLConfigBuilder 的父类,并且它还有其他的子类。

protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;

public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //解析configuration节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

后调用 parseConfiguration(XNode root) 方法

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      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"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

2.加载Mapper映射配置文件

XMLMapperBuilder.java
public void parse() {
    // <1> 判断当前 Mapper 是否已经加载过
    if (!configuration.isResourceLoaded(resource)) {
        // <2> 解析 `` 节点
        configurationElement(parser.evalNode("/mapper"));
        // <3> 标记该 Mapper 已经加载过
        configuration.addLoadedResource(resource);
        // <4> 绑定 Mapper
        bindMapperForNamespace();
    }

    // <5> 解析待定的  节点
    parsePendingResultMaps();
    // <6> 解析待定的  节点
    parsePendingCacheRefs();
    // <7> 解析待定的 SQL 语句的节点
    parsePendingStatements();
}

<2>configurationElement(XNode context) 方法,解析  节点。

 

 

// XMLMapperBuilder.java

private void configurationElement(XNode context) {
    try {
        // <1> 获得 namespace 属性
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
        // <1> 设置 namespace 属性
        builderAssistant.setCurrentNamespace(namespace);
        // <2> 解析  节点
        cacheRefElement(context.evalNode("cache-ref"));
        // <3> 解析  节点
        cacheElement(context.evalNode("cache"));
        // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // <4> 解析  节点们
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // <5> 解析  节点们
        sqlElement(context.evalNodes("/mapper/sql"));
        // <6> 解析  节点们。

private void buildStatementFromContext(List list) {
        if(this.configuration.getDatabaseId() != null) {
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }

        this.buildStatementFromContext(list, (String)null);
    }


private void buildStatementFromContext(List list, String requiredDatabaseId) {
    // <1> 遍历  节点们,逐个创建 XMLStatementBuilder 对象,执行解析。

(2) 处,解析失败,调用 Configuration 的addIncompleteStatement(XMLStatementBuilder incompleteStatement) 方法,添加到 configuration 中。

bindMapperForNamespace() 方法,绑定 Mapper 。

private void bindMapperForNamespace() {
//获得 Mapper 映射配置文件对应的 Mapper 接口,实际上类名就是 namespace 。
        String namespace = this.builderAssistant.getCurrentNamespace();
        if(namespace != null) {
            Class boundType = null;

            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {
                ;
            }

            if(boundType != null && !this.configuration.hasMapper(boundType)) {
                this.configuration.addLoadedResource("namespace:" + namespace);
                this.configuration.addMapper(boundType);
            }
        }

    }
}
this.parsePendingResultMaps();
this.parsePendingChacheRefs();
this.parsePendingStatements();

思路一样的:1)获得对应的集合;2)遍历集合,执行解析;3)执行成功,则移除出集合;4)执行失败,忽略异常。

 

org.apache.ibatis.builder.MapperBuilderAssistant ,继承 BaseBuilder 抽象类,Mapper 构造器的小助手,提供了一些公用的方法,例如创建 ParameterMap、MappedStatement 对象等等。

加载 Statement 配置。而这个步骤的入口是 XMLStatementBuilder 。
 

org.apache.ibatis.builder.xml.XMLStatementBuilder ,继承 BaseBuilder 抽象类,Statement XML 配置构建器,主要负责解析 Statement 配置,即