mybatis源码的分析

mybatis源码仓库地址

mybatis文档

整体架构

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

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

解析占位符:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

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> 解析 `<mapper />` 节点
        configurationElement(parser.evalNode("/mapper"));
        // <3> 标记该 Mapper 已经加载过
        configuration.addLoadedResource(resource);
        // <4> 绑定 Mapper
        bindMapperForNamespace();
    }

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

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

 

 

// 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> 解析 <cache-ref /> 节点
        cacheRefElement(context.evalNode("cache-ref"));
        // <3> 解析 <cache /> 节点
        cacheElement(context.evalNode("cache"));
        // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // <4> 解析 <resultMap /> 节点们
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // <5> 解析 <sql /> 节点们
        sqlElement(context.evalNodes("/mapper/sql"));
        // <6> 解析 <select /> <insert /> <update /> <delete /> 节点们
        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);
    }
}
private void cacheRefElement(XNode context) {
  if (context != null) {
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}
private void cacheElement(XNode context) throws Exception {
    if (context != null) {
        // <1> 获得负责存储的 Cache 实现类
        String type = context.getStringAttribute("type", "PERPETUAL");
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        // <2> 获得负责过期的 Cache 实现类
        String eviction = context.getStringAttribute("eviction", "LRU");
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        // <3> 获得 flushInterval、size、readWrite、blocking 属性
        Long flushInterval = context.getLongAttribute("flushInterval");
        Integer size = context.getIntAttribute("size");
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        boolean blocking = context.getBooleanAttribute("blocking", false);
        // <4> 获得 Properties 属性
        Properties props = context.getChildrenAsProperties();
        // <5> 创建 Cache 对象
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
}

3解析resultMap

org.apache.ibatis.builder.ResultMapResolver,ResultMap 解析器。

4解析sql标签

 private void sqlElement(List<XNode> list) throws Exception {
        if(this.configuration.getDatabaseId() != null) {
            this.sqlElement(list, this.configuration.getDatabaseId());
        }

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


private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {、
//遍历所有的sql节点

        Iterator i$ = list.iterator();

        while(i$.hasNext()) {
            XNode context = (XNode)i$.next();
//  获得 databaseId 属性
            String databaseId = context.getStringAttribute("databaseId");
//获得完整的 id 属性,格式为 `${namespace}.${id}`
            String id = context.getStringAttribute("id");
            id = this.builderAssistant.applyCurrentNamespace(id, false);
//判断 databaseId 是否匹配
            if(this.databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//添加到 sqlFragments 中
                this.sqlFragments.put(id, context);
            }
        }

    }

 

 5.buildStatementFromContext(List<XNode> list) 方法,解析 <select /><insert /><update /><delete /> 节点们。

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

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


private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    // <1> 遍历 <select /> <insert /> <update /> <delete /> 节点们
    for (XNode context : list) {
        // <1> 创建 XMLStatementBuilder 对象,执行解析
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            // <2> 解析失败,添加到 configuration 中
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

(1)遍历 <select /><insert /><update /><delete /> 节点们,逐个创建 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 配置,即 <select /><insert /><update /><delete /> 标签。

 private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        Iterator i$ = list.iterator();

        while(i$.hasNext()) {
            XNode context = (XNode)i$.next();
            XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

            try {
                statementParser.parseStatementNode();
            } catch (IncompleteElementException var7) {
                this.configuration.addIncompleteStatement(statementParser);
            }
        }

    }

// XMLStatementBuilder.java

public void parseStatementNode() {
    // <1> 获得 id 属性,编号。
    String id = context.getStringAttribute("id");
    // <2> 获得 databaseId , 判断 databaseId 是否匹配
    String databaseId = context.getStringAttribute("databaseId");
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }

    // <3> 获得各种属性
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");

    // <4> 获得 lang 对应的 LanguageDriver 对象
    LanguageDriver langDriver = getLanguageDriver(lang);

    // <5> 获得 resultType 对应的类
    Class<?> resultTypeClass = resolveClass(resultType);
    // <6> 获得 resultSet 对应的枚举值
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    // <7> 获得 statementType 对应的枚举值
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

    // <8> 获得 SQL 对应的 SqlCommandType 枚举值
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // <9> 获得各种属性
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    // <10> 创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // <11> 解析 <selectKey /> 标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // <12> 创建 SqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // <13> 获得 KeyGenerator 对象
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    // <13.1> 优先,从 configuration 中获得 KeyGenerator 对象。如果存在,意味着是 <selectKey /> 标签配置的
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    // <13.2> 其次,根据标签属性的情况,判断是否使用对应的 Jdbc3KeyGenerator 或者 NoKeyGenerator 对象
    } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys", // 优先,基于 useGeneratedKeys 属性判断
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) // 其次,基于全局的 useGeneratedKeys 配置 + 是否为插入语句类型
                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 创建 MappedStatement 对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

org.apache.ibatis.builder.xml.XMLIncludeTransformer ,XML <include /> 标签的转换器,负责将 SQL 中的 <include /> 标签转换成对应的 <sql /> 的内容。

// XMLIncludeTransformer.java

private final Configuration configuration;
private final MapperBuilderAssistant builderAssistant;

public XMLIncludeTransformer(Configuration configuration, MapperBuilderAssistant builderAssistant) {
    this.configuration = configuration;
    this.builderAssistant = builderAssistant;
}

 

加载注解配置。而这个部分的入口是 MapperAnnotationBuilder 。

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder ,Mapper 注解构造器,负责解析 Mapper 接口上的注解。

// MapperAnnotationBuilder.java

public void parse() {
    // <1> 判断当前 Mapper 接口是否应加载过。
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // <2> 加载对应的 XML Mapper
        loadXmlResource();
        // <3> 标记该 Mapper 接口已经加载过
        configuration.addLoadedResource(resource);
        // <4> 设置 namespace 属性
        assistant.setCurrentNamespace(type.getName());
        // <5> 解析 @CacheNamespace 注解
        parseCache();
        // <6> 解析 @CacheNamespaceRef 注解
        parseCacheRef();
        // <7> 遍历每个方法,解析其上的注解
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            try {
                // issue #237
                if (!method.isBridge()) {
                    // <7.1> 执行解析
                    parseStatement(method);
                }
            } catch (IncompleteElementException e) {
                // <7.2> 解析失败,添加到 configuration 中
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    // <8> 解析待定的方法
    parsePendingMethods();
}

loadXmlResource() 方法,加载对应的 XML Mapper 

parseStatement(Method method) 方法,解析方法上的 SQL 操作相关的注解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值