Mybatis架构
1、整体架构
注:该内容来源于《Mybatis技术内幕》一书。
MyBatis的整体架构分为三层, 分别是基础支持层、核心处理层和接口层。
1>、基础支撑层
基础支撑层主要包括了数据源模块、事务管理模块、缓存模块、Bingding模块、反射模块、类型转换、日志模块、资源加载、解析器模块等。
- 反射模块
Mybatis中专门提供了反射模块,该模块对Java 原生的反射进行了良好的封装,提供了更加简洁易用的API ,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。 - 类型转换
类型转换模块主要提供以下功能:1、为简化配置文件提供了别名机制;2、实现JDBC 类型与Java 类型之间的转换,该功能在为SQL 语句绑定实参以及映射查询结果集时都会涉及。在为SQL 语句绑定实参时, 会将数据由Java 类型转换成JDBC 类型;而在映射结果集时,会将数据由JDB C 类型转换成Java 类型。 - 日志模块
日志模块主要功能就是集成第三方日志框架,比如:Log4j 、Log4j2, slf4j、CommonsLog、JdkLog等。 - 资源加载模块
资源加载模块主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能。 - 解析器模块
解析器模块的主要提供了两个功能: 一个功能是对XPath 进行封装,为MyBatis 初始化时解析mybatis-config.xml 配置文件以及映射配置文件提供支持;另一个功能是为处理动态SQL 语句中的占位符提供支持。 - 数据源模块
MyBatis 自身提供了相应的数据源实现,MyBatis 也提供了与第三方数据源集成的接口。 - 事务管理
MyBatis对数据库中的事务进行了抽象,其自身提供了相应的事务接口和简单实现。在很多场景中,MyBatis会与Spring框架集成,并由Spring框架管理事务。 - 缓存模块
MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。这里需要注意的是, MyBatis中自带的这两级缓存与MyBatis以及整个应用是运行在同一个JVM中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用Redis 、Memcache 等缓存产品。 - 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);
}
总结,方法执行过程中,类和方法的调用顺序如下图所示:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68921.html