MyBatis架构源码分析
如果不太熟悉MyBatis使用的请先参见MyBatis官方文档
从官方文档上面的功能介绍及配置来看,我们入手的方向,就是xml配置,xml映射,动态sql,javaAPI,SQL语句构建器,日志等方面入手。
官方介绍:
什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
使用mybaties基础,就是依赖包
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version> </dependency>
从 XML 中构建 SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
一。
ORM是Object和Relation之间的映射,包括Object->Relation和Relation->Object两方面。Hibernate是个完整的ORM框架,而MyBatis完成的是Relation->Object,
也就是其所说的data mapper framework。关于ORM的一些设计思路和细节可以参见Martin Flow《企业应用架构模式》一书中的ORM章节,MyBatis并不刻意于完成ORM(对象映射)的完整概念,
而是旨在更简单、更方便地完成数据库操作功能,减轻开发人员的工作量,我想这对于应用系统来说也是最实用的,相信用Hibernate的都受过它的痛苦,而用过MyBatis的都会感觉它很简捷轻松。
二。
而下面是MyBatis源码包对应的架构图
https://images0.cnblogs.com/blog/571766/201405/202018523246078.png
1.接口层
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
lSessionFactory、SqlSession这是MyBatis接口层的核心类,尤其是SqlSession,是实现所有数据库操作的API,
这几个类都是org.apache.ibatis.session包下的
Configuration对象与DefaultSqlSessionFactory是1:1的关联关系,这也就意味着在一个DefaultSqlSessionFactory衍生出来的所有SqlSession作用域里,Configuration对象是全局唯一的
同时SqlSessionFactory提供了getConfiguration()接口来公开Configuration对象,因此开发者除了配置文件之外,还可以在程序里动态更改Configuration的属性项以达到动态调整的目的,但此时不仅要考虑到执行完reset,同时还要考虑在修改过程中会可能影响到其他SqlSession的执行
2.核心层
在应用启动的时候,MyBatis解析两种配置文件
- SqlMapConfig.xml
- SqlMap.xml
SqlMapConfig.xml是在XMLConfigBuilder类中完成解析的
我们知道XML有两种解析方式:一是DOM,另一个是SAX,MyBatis使用的是org.wrc.dom——JDK提供的文档对象模型(DOM)接口(SqlMapConfig.xml并不大,所以DOM方式并没有什么效率损耗,JDK也提供了SAX模型接口org.xml.sax,这两个都是JAXP的组件API),以及JDK官方提供的javax.xml.xpath.XPath来作为XML路径寻找组件。
SqlMap.xml是在XMLMapperBuilder中解析完成的,其中把对Statement的解析(即SqlMap.xml中SELECT|INSERT|UPDATE|DELETE定义部分)委托给XMLStatementBuilder来完成。SqlMap.xml的解析比较复杂的,涉及到PreparedMapping、ResultMapping、LanguageDriver、Discriminator、缓存、自动映射等一系列对象的构造,
MyBatis中Executor是的核心,围绕着它完成了数据库操作的完整过程。
二、整体架构
下面是从功能流程层次描述MyBatis的整体架构图
而下面是MyBatis源码包对应的架构图
下面以“功能流程角度的架构图”来简要地分析下各层的架构,在后面系列文章中将有专题来深入解析MyBatis重要的功能点。
1、接口层
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
SqlSessionFactory、SqlSession这是MyBatis接口层的核心类,尤其是SqlSession,是实现所有数据库操作的API,这几个类都是org.apache.ibatis.session包下的,这个包的主体类结构图如下
Configuration是MyBatis中相当重要的一个类,可以这么说,如果理解了其中的所有参数的意义,不仅清楚地知道MyBatis提供的所有配置项,还理解了MyBatis的内部核心运行原理,当然要真正理解这些参数的意义及实现,还需要阅读完完整的MyBatis框架之后才能做到。
由上图可以看到,Configuration对象与DefaultSqlSessionFactory是1:1的关联关系,这也就意味着在一个DefaultSqlSessionFactory衍生出来的所有SqlSession作用域里,Configuration对象是全局唯一的。同时SqlSessionFactory提供了getConfiguration()接口来公开Configuration对象,因此开发者除了配置文件之外,还可以在程序里动态更改Configuration的属性项以达到动态调整的目的,但此时不仅要考虑到执行完reset,同时还要考虑在修改过程中会可能影响到其他SqlSession的执行。
2、核心层
2.1 配置解析
在应用启动的时候,MyBatis解析两种配置文件
- SqlMapConfig.xml
- SqlMap.xml
SqlMapConfig.xml是在XMLConfigBuilder类中完成解析的,其类图关系大致如下
我们知道XML有两种解析方式:一是DOM,另一个是SAX,MyBatis使用的是org.wrc.dom——JDK提供的文档对象模型(DOM)接口(SqlMapConfig.xml并不大,所以DOM方式并没有什么效率损耗,JDK也提供了SAX模型接口org.xml.sax,这两个都是JAXP的组件API),以及JDK官方提供的javax.xml.xpath.XPath来作为XML路径寻找组件。
SqlMap.xml是在XMLMapperBuilder中解析完成的,其中把对Statement的解析(即SqlMap.xml中SELECT|INSERT|UPDATE|DELETE定义部分)委托给XMLStatementBuilder来完成。SqlMap.xml的解析比较复杂的,涉及到PreparedMapping、ResultMapping、LanguageDriver、Discriminator、缓存、自动映射等一系列对象的构造,这里暂时略过,后面专题分析。
2.2 SQL执行
MyBatis中Executor是的核心,围绕着它完成了数据库操作的完整过程。下面是Executor的类图
在上图中我列出了Executor中方法的参数,而在其子类中就没有明确写出。从上图中可以看到,Executor主要提供了
- QUERY|UPDATE(INSERT和DELETE也是使用UPDATE),从方法定义中可看到,它需要MappedStatement、parameter、resultHandler这几个实例对象,这几个也是SQL执行的主要部分,详细实现在后面专题中再介绍。
- 事务提交/回滚,这委托给Transaction对象来完成。
- 缓存,createCacheKey()/isCached()。
- 延迟加载,deferload()。
- 关闭,close(),主要是事务回滚/关闭。
BaseExecutor的属性表明:它内部维护了localCache来localOutputParameterCache来处理缓存,至于这缓存保存的是什么,这后面专题再说。以及线程安全的延迟加载列表deferredLoads、事务对象Transaction。
BatchExecutor的属性已经表明:它内部维护了StatementList批量提交并通过batchResultList保存执行结果。
ResueExecutor的属性及方法表明:它内部维护了java.sql.Statement对象缓存,以重用Statement对象(对于支持预编译的数据库而言,在创建PreparedStatement时需要发送一次数据库请求预编译,而重用Statement对象主要是减少了这次预编译的网路开销)。
下面以SqlSession.selectList为例,画出SQL执行的时序图(点击下方的图片查看大图,部分分支有所简化)
3、基础层
3.1、logging:
MyBatis使用了自己定义的一套logging接口,根据开发者常使用的日志框架——Log4j、Log4j2、Apache Commons Log、java.util.logging、slf4j、stdout(控制台)——分别提供了适配器。由于各日志框架的Log级别分类法有所不同(比如java.util.logging.Level提供的是All、FINEST、FINER、FINE、CONFIG、INFO、WARNING、SEVERE、OFF这九个级别,与通常的日志框架分类法不太一样),MyBatis统一提供trace、debug、warn、error四个级别,这基本与主流框架分类法是一致的(相比而言缺少Info,也许MyBatis认为自己的日志要么是debug需要的,要么就至少是warn,没有Info的必要)。
在org.apache.ibatis.logging里还有个比较特殊的包jdbc,这不是按字面意义理解把日志通过jdbc记录到数据库里,而是将jdbc操作以开发者配置的日志框架打印出来,这也就是我们在开发阶段常用的跟踪SQL语句、传入参数、影响行数这些重要的调试信息。
3.2、IO
MyBatis里的IO主要是包含两大功能:提供读取资源文件的API、封装MyBatis自身所需要的ClassLoader和加载顺序。
3.3、reflection
在MyBatis如参数处理、结果映射这些大量地使用了反射,需要频繁地读取Class元数据、反射调用get/set,因此MyBatis提供了org.apache.ibatis.reflection对常见的反射操作进一步封装,以提供更简洁方便的API。比如我们reflect时总是要处理异常(IllegalAccessException、NoSuchMethodException),MyBatis统一处理为自定义的RuntimeException,减少代码量。
3.4、exceptions
在以Spring为代表的开源框架中,对于应用程序中无法进一步处理的异常大都转成RuntimeException来方便调用者操作,另外如频繁遇到的SQLException,JDK约定其是个Exception,从JDK的角度考虑,强制要求开发者捕获SQLException是为了能在catch/finally中关闭数据库连接,而Spring之类的框架为开发者做了资源管理的事情,自然就不需要开发者再烦心SQLException,因此封装转换成RuntimeException。MyBatis的异常体系不复杂,org.apache.ibatis.exceptions下就几个类,主要被使用的是PersistenceException。
3.5、缓存
缓存是MyBatis里比较重要的部分,有两种缓存:
- SESSION或STATEMENT作用域级别的缓存,默认是SESSION,BaseExecutor中根据MappedStatement的Id、SQL、参数值以及rowBound(边界)来构造CacheKey,并使用BaseExccutor中的localCache来维护此缓存。
- 全局的二级缓存,通过CacheExecutor来实现,其委托TransactionalCacheManager来保存/获取缓存,这个全局二级缓存比较复杂,后面还需要专题分析,至于其缓存的效率以及应用场景也留到那时候再分析。
3.6、数据源/连接池
MyBatis自身提供了一个简易的数据源/连接池,在org.apache.ibatis.datasource下,后面专题分析。主要实现类是PooledDataSource,包含了最大活动连接数、最大空闲连接数、最长取出时间(避免某个线程过度占用)、连接不够时的等待时间,虽然简单,却也体现了连接池的一般原理。阿里有个“druid”项目,据他们说比proxool、c3p0的效率还要高,可以学习一下。
3.7 事务
MyBatis对事务的处理相对简单,TransactionIsolationLevel中定义了几种隔离级别,并不支持内嵌事务这样较复杂的场景,同时由于其是持久层的缘故,所以真正在应用开发中会委托Spring来处理事务实现真正的与开发者隔离。分析事务的实现是个入口,借此可以了解不扫JDBC规范方面的事情。
后续将对MyBatis各个部分做详细的设计及源代码分析,由于读取和解析SqlMapConfig.xml和SqlMap.xml的逻辑与各个模块的相关性较强,因此将把这部分内容与在各模块组合在一起分析。