您的位置:首页 > 运维架构 > 网站架构

MyBatis架构设计及源代码分析系列(一):MyBatis架构

2015-07-16 00:00 639 查看
如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处。

一、概述

MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己

TheMyBatisdatamapperframeworkmakesiteasiertousearelationaldatabasewithobject-orientedapplications.MyBatiscouplesobjectswithstoredproceduresorSQLstatementsusingaXMLdescriptororannotations.SimplicityisthebiggestadvantageoftheMyBatisdatamapperoverobjectrelationalmappingtools.

而在其官方文档中介绍“WhatisMyBaits”中说到

MyBatisisafirstclasspersistenceframeworkwithsupportforcustomSQL,storedproceduresandadvancedmappings.MyBatiseliminatesalmostalloftheJDBCcodeandmanualsettingofparametersandretrievalofresults.MyBatiscanusesimpleXMLorAnnotationsforconfigurationandmapprimitives,MapinterfacesandJavaPOJOs(PlainOldJavaObjects)todatabaserecords.

ORM是Object和Relation之间的映射,包括Object->Relation和Relation->Object两方面。Hibernate是个完整的ORM框架,而MyBatis完成的是Relation->Object,也就是其所说的datamapperframework。关于ORM的一些设计思路和细节可以参见MartinFlow《企业应用架构模式》一书中的ORM章节,MyBatis并不刻意于完成ORM(对象映射)的完整概念,而是旨在更简单、更方便地完成数据库操作功能,减轻开发人员的工作量,我想这对于应用系统来说也是最实用的,相信用Hibernate的都受过它的痛苦,而用过MyBatis的都会感觉它很简捷轻松。

二、整体架构

下面是从功能流程层次描述MyBatis的整体架构图





而下面是MyBatis源码包对应的架构图





下面以“功能流程角度的架构图”来简要地分析下各层的架构,在后面系列文章中将有专题来深入解析MyBatis重要的功能点。

1、接口层

我们知道,在不考虑与Spring集成的情况下,使用MyBatis执行数据库操作的代码如下

Stringresource="org/mybatis/example/mybatis-config.xml";
InputStreaminputStream=Resources.getResourceAsStream(resource);

SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream);
SqlSessionsession=sqlSessionFactory.openSession();

try{
Blogblog=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.2SQL执行

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、ApacheCommonsLog、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的逻辑与各个模块的相关性较强,因此将把这部分内容与在各模块组合在一起分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: