下面是从功能流程层次描述MyBatis的整体架构图
而下面是MyBatis源码包对应的架构图
下面以“功能流程角度的架构图”来简要地分析下各层的架构,在后面系列文章中将有专题来深入解析MyBatis重要的功能点。
我们知道,在不考虑与Spring集成的情况下,使用MyBatis执行数据库操作的代码如下
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的执行。
在应用启动的时候,MyBatis解析两种配置文件
我们知道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是的核心,围绕着它完成了数据库操作的完整过程。下面是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对象主要是减少了这次预编译的网路开销)。
日志接口和常用日志组件的实现,还包含了对JDBC底层Connection, Statement等对象的日志代理。
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语句、传入参数、影响行数这些重要的调试信息。
这个包中最主要的两个类是Resources和ResolverUtil,用于获取资源,根据指定条件获取类。 提供读取资源文件的API、封装MyBatis自身所需要的ClassLoader和加载顺序。
反射工具类,也是MyBatis 结果映射最重要的工具类。 在MyBatis中开发插件时,可以使用SystemMetaObject工具类创建MetaObject对象,使用这个工具可以很方便地获取和修改对象的值,这是一个很强大的工具类。 另外ParamNameUtil支持在使用JDK8时直接获取参数名(可以省略@Param注解),在使用该工具类之前可以通过JDK.parameterExists判断当前环境是否为JDK8以上。
(在MyBatis如参数处理、结果映射这些大量地使用了反射,需要频繁地读取Class元数据、反射调用get/set,因此MyBatis提供了org.apache.ibatis.reflection对常见的反射操作进一步封装,以提供更简洁方便的API。比如我们reflect时总是要处理异常(IllegalAccessException、NoSuchMethodException),MyBatis统一处理为自定义的RuntimeException,减少代码量。)
在以Spring为代表的开源框架中,对于应用程序中无法进一步处理的异常大都转成RuntimeException来方便调用者操作,另外如频繁遇到的SQLException,JDK约定其是个Exception,从JDK的角度考虑,强制要求开发者捕获SQLException是为了能在catch/finally中关闭数据库连接,而Spring之类的框架为开发者做了资源管理的事情,自然就不需要开发者再烦心SQLException,因此封装转换成RuntimeException。MyBatis的异常体系不复杂,org.apache.ibatis.exceptions下就几个类,主要被使用的是PersistenceException。
缓存是MyBatis里比较重要的部分,有两种缓存:
SESSION或STATEMENT作用域级别的缓存,默认是SESSION,BaseExecutor中根据MappedStatement的Id、SQL、参数值以及rowBound(边界)来构造CacheKey,并使用BaseExccutor中的localCache来维护此缓存。
全局的二级缓存,通过CacheExecutor来实现,其委托TransactionalCacheManager来保存/获取缓存,这个全局二级缓存比较复杂,后面还需要专题分析,至于其缓存的效率以及应用场景也留到那时候再分析。
MyBatis自身提供了一个简易的数据源/连接池,在org.apache.ibatis.datasource下,后面专题分析。主要实现类是PooledDataSource,包含了最大活动连接数、最大空闲连接数、最长取出时间(避免某个线程过度占用)、连接不够时的等待时间,虽然简单,却也体现了连接池的一般原理。阿里有个“druid”项目,据他们说比proxool、c3p0的效率还要高,可以学习一下。
事务接口和实现类,提供了JDBC事务和外部的事务管理。MyBatis对事务的处理相对简单,TransactionIsolationLevel中定义了几种隔离级别,并不支持内嵌事务这样较复杂的场景,同时由于其是持久层的缘故,所以真正在应用开发中会委托Spring来处理事务实现真正的与开发者隔离。分析事务的实现是个入口,借此可以了解不扫JDBC规范方面的事情。
包含注解方式需要用到的所有注解,主要分为普通的CRUD注解、Provider类注解、配置型注解。
绑定接口和映射语句,使用JDK动态代理实现。
映射语句的构造器, 包含注解和XML两种方式,大量使用建造者模式。
包含了Executor 接口和几个实现类,二级缓存就是通过
CacheExecutor装饰类实现的。这个包还包含以下几个子包。
包含了ResultMap、 ParameterMap等与映射相关的配置,还有对底层JDBC的封装。
XML解析实现,可以用于Mapper.xml和MyBatis配置文件的解析。
SqlSession接口及实现类,还有主要功能类。 这个包用于对Executor和MappedStatement进行封装。
Java类型和JDBC类型转换器,MyBatis在对参数和结果进行转换时,通过这些类型转换器来实现,当需要实现自己的类型转换器时,可以参考此处介绍的大量示例。
以上内容是对各个包的大概介绍,在阅读源码或者学习MyBatis 时, 还有比源码更有价值
的内容,那就是测试。MyBatis 针对各个具体的类和功能提供了全面的测试