Mybatis源码学习(2)-整体架构概述

Mybatis架构

1、整体架构

  注:该内容来源于《Mybatis技术内幕》一书。
  MyBatis的整体架构分为三层, 分别是基础支持层、核心处理层和接口层。
在这里插入图片描述

1>、基础支撑层

  基础支撑层主要包括了数据源模块、事务管理模块、缓存模块、Bingding模块、反射模块、类型转换、日志模块、资源加载、解析器模块等。

  1. 反射模块
      Mybatis中专门提供了反射模块,该模块对Java 原生的反射进行了良好的封装,提供了更加简洁易用的API ,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。
  2. 类型转换
      类型转换模块主要提供以下功能:1、为简化配置文件提供了别名机制;2、实现JDBC 类型与Java 类型之间的转换,该功能在为SQL 语句绑定实参以及映射查询结果集时都会涉及。在为SQL 语句绑定实参时, 会将数据由Java 类型转换成JDBC 类型;而在映射结果集时,会将数据由JDB C 类型转换成Java 类型。
  3. 日志模块
      日志模块主要功能就是集成第三方日志框架,比如:Log4j 、Log4j2, slf4j、CommonsLog、JdkLog等。
  4. 资源加载模块
       资源加载模块主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能。
  5. 解析器模块
       解析器模块的主要提供了两个功能: 一个功能是对XPath 进行封装,为MyBatis 初始化时解析mybatis-config.xml 配置文件以及映射配置文件提供支持;另一个功能是为处理动态SQL 语句中的占位符提供支持。
  6. 数据源模块
       MyBatis 自身提供了相应的数据源实现,MyBatis 也提供了与第三方数据源集成的接口。
  7. 事务管理
       MyBatis对数据库中的事务进行了抽象,其自身提供了相应的事务接口和简单实现。在很多场景中,MyBatis会与Spring框架集成,并由Spring框架管理事务。
  8. 缓存模块
       MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。这里需要注意的是, MyBatis中自带的这两级缓存与MyBatis以及整个应用是运行在同一个JVM中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用Redis 、Memcache 等缓存产品。
  9. Binging模块
       Binging模块主要是将面向mapper接口编程转换成session中对应的方法执行。
2>、核心处理层

  核心处理层主要包括了配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件等模块。
10. 配置解析
  在MyBatis初始化过程中,会加载mybatis-config.xml 配置文件、映射配置文件以及Mapper接口中的注解信息,解析后的配置信息会形成相应的对象并保存到Configuration 对象中。
11. SQL解析
  拼凑SQL 语句是一件烦琐且易出错的过程,为了将开发人员从这项枯燥无趣的工作中解脱出来, MyBatis 实现动态SQL语句的功能,提供了多种动态SQL语句对应的节点,比如:<where>、<if>、<foreeach>等节点。MyBatis中的scripting模块会根据用户传入的实参,解析映射文件中定义的动态SQL节点,并形成数据库可执行的SQL 语句。之后会处理SQL 语句中的占位符,绑定用户传入的实参。
12. SQL执行
  SQL 语句的执行涉及多个组件,其中比较重要的是Executor、StatementHandler、
ParameterHandler和ResultSetHandler。Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作委托给StatementHandler完成。StatementHandler首先通过ParameterHandler 完成SQL 语句的实参绑定,然后通过java.sql.Statement对象执行SQL语句并得到结果集,最后通过ResultSetHandler完成结果集的映射,得到结果对象并返回。
13. 插件
  Mybatis 自身的功能虽然强大,但是并不能完美切合所有的应用场景,因此MyBatis提供了插件接口,我们可以通过添加用户自定义插件的方式对MyBati s 进行扩展。用户自定义插件也可以改变Mybatis 的默认行为,例如,我们可以拦截SQL 语句并对其进行重写。由于用户自定义插件会影响MyBatis 的核心行为,在使用自定义插件之前,开发人员需要了解MyBatis内部的原理,这样才能编写出安全、高效的插件。

3>、接口层

  接口层相对简单,其核心是SqlSession 接口,该接口中定义了MyBatis暴露给应用程序调用的API ,也就是上层应用与MyBatis交互的桥梁。接口层在接收到调用请求时,会调用核心处理层的相应模块来完成具体的数据库操作。

2、源码包结构

  源码包机构如下图所示:
在这里插入图片描述
14. org.apache.ibatis.annotations包 (核心处理层—配置解析)
主要包含了所有mapper接口中用到的注解,比如常用的@Param、@Update、@Select、@Delete等。
15. org.apache.ibatis.binging包 (基础支撑层—Binding模块)
将mapper接口绑定配置Mapper语句。
16. org.apache.ibatis.builder包(核心处理层—配置解析)
实现配置对象(包括XML、注解)的构建,比如Configuration等。
17. org.apache.ibatis.cache包(基础支撑层—缓存模块)
缓存功能实现。包括各类装饰器等。
18. org.apache.ibatis.cursor包(核心处理层—SQL执行)
实现游标方式查询数据、游标适合处理大数据量的查询操作,通常情况下不适合一次性加载到内存中的这种查询方式。
19. org.apache.ibatis.datasource包(基础支撑层—数据源)
数据源相关类
20. org.apache.ibatis.exceptions包(基础基础层,异常基类)
Mybatis中异常类的基类。
21. org.apache.ibatis.executor包(核心处理层—SQL执行)
SQL语句执行器,核心包
22. org.apache.ibatis.io包(基础支撑层—资源加载)
资源文件读取
23. org.apache.ibatis.jdbc包(基础支撑层—数据源)
JDBC一些操作。
24. org.apache.ibatis.lang包(基础基础层,工具类)
这个包只有2个注解 @UsesJava7 @UsesJava8 使用这个两个注解标识哪些可以使用JDK7 API 哪些可以使用JDK8 API
25. org.apache.ibatis.logging包(基础支撑层—日志模块)
日志功能,实现各类日志框架的对接。
26. org.apache.ibatis.mapping包(核心处理层—参数映射、结果集映射等)
配置文件与实体对象的映射功能,比如:Mapper映射、参数映射、结果集映射等。
27. org.apache.ibatis.parsing包(基础支撑层—解析器模块)
解析工具包
28. org.apache.ibatis.plugin包(核心处理层—插件)
拦截器功能实现
29. org.apache.ibatis.reflection包(基础支撑层—反射模块)
反射器功能,这个包下实现了元数据编程。
30. org.apache.ibatis.scripting包(核心处理层—配置解析)
动态SQL语句实现。比如配置文件中的、等节点
31. org.apache.ibatis.session包(接口层)
主要实现SqlSession功能,核心包。
32. org.apache.ibatis.transaction包(基础支撑层—数据源)
事务功能实现
33. org.apache.ibatis.type包(基础支撑层—类型转换)
类型处理功能实现

3、SQL执行过程

在这里插入图片描述
  结合上一篇《源码环境搭建》中的例子,分析Mybatis执行一次SQL语句的过程,大致如下:

  • 第一步:SqlSession对象初始化
    1、加载配置文件—Mybatis配置文件
    2、通过SqlSessionFactoryBuilder构建类构建SqlSessionFactory实例对象。在构建SqlSessionFactory对象的过程中,解析了Mybatis配置文件。其中,通过解析配置文件中的节点,把所有映射文件也进行了解析。该过程最终会把所有的配置信息解析,并生成Configuration对象。
    3、根据上一步生成的Configuration对象,创建了SqlSessionFactory实例对象(即实现类DefaultSqlSessionFactory)。
    4、使用工厂类SqlSessionFactory创建SqlSession实例对象。
  • 第二步:SQL语句执行
    以执行sqlSession.selectList(String statement, Object parameter);代码为例。
    1、在DefaultSqlSession的selectList方法中根据statement字符串,生成MappedStatement对象,即SQL语句节点的解析处理。
    2、执行Executor类中的query方法,最终调用的是抽象类BaseExecutor中的query–>queryFromDatabase–>doQuery方法。其中,doQuery是一个抽象方法,本例中实现该方法的地方在SimpleExecutor类中。
    3、在SimpleExecutor类中的doQuery方法中,首先创建了StatementHandler实例对象,然后根据StatementHandler实例对象创建了Statement对象,最后执行了StatementHandler实例对象的query方法。具体代码如下:
@Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

4、上一步中StatementHandler实例对象实际上就是RoutingStatementHandler实例对象。即实际执行了RoutingStatementHandler实例对象的query方法。该方法中又调用了PreparedStatementHandler对象中的query方法。代码如下:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

5、PreparedStatementHandler的query方法中实际上通过java.sql.PreparedStatement的execute方法,实现了数据库查询。然后查询结果集由resultSetHandler进行处理,并进行返回。结果集处理方法代码如下:

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

总结,方法执行过程中,类和方法的调用顺序如下图所示:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姠惢荇者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值