关于执行器包executor与其子包下的所有文件,是整个框架非常核心的部分,在代码重构之后,结构要比以前好很多
整个框架在jdbc的执行上除去那些辅助代码:如配置文件解析(从配置文件构建已映射语句及其它一些属性);
缓存系统;反射工具;数据源等,最终那些与jdbc执行相关的代码并不是太多,也不是很复杂,至少重构之后我认为
较之以前要好理解多了.
整个框架从上层到下层我认为主要可以分为以下几个层次:
A.session层:
这一层是与程序离的最近的一个层次,程序员在要求框架执行持久化操作时,就是在这个层次上
进行调用的,它的主要作用就是为程序员规范了一个接口,让所有程序以一个统一的规范来使用框架,但是执行
jdbc操作的并不是由这一层来完成,这个层次在完成操作时是委托一个Executor来完成的,即下一个层次.
对于这一个层还要明确一点,就是每一个线程执行操作都应该创建一个新的session对象,而不是共享一个session
对象,这个对象类似于hibernate的session是不支持并发的
B.executor层:
这一层对session的接口进行了归类处理,使大部分相似的操作可以共享代码,在这个层次框架做的
主要工作是什么呢?通过分析可以看到在这个层次的代码主要被抽象出了二级:第一级是一个抽象类级别主要用于缓存处理
与延迟加载,这个抽象类是每种执行器需要共享的代码;第二级是具体的某一种类型的执行器(共三种),具体选择那一种,与创建session
时所指定的参数有关系,如果没有指定参数,则会使用Configuration中配置的默认类型进行executor对象的创建
它将session的功能进行相似共享,最后抽象出了三个方法:即doUpdate,doFlushStatements,doQuery
第一个具体的子类就是要完成这三个操作,子类完成三个操作的过程都是有规律,它们总是委托一个名叫StatementHandler
处理器去完成实际的操作,那么StatementHandler又是如何完成操作的呢?
C.StatementHandler:
这一层基本是对jdbc操作的"八股文"抽象,可这么说这个层次是与jdbc操作最贴近的地方,你可以想象
一下,你在执行jdbc操作的时候,你会分几步来做:1.创建statement;2.设置statement参数;3.执行statement语句获取结果
这基本是关于statement对象处理的最重要的三步.至于之前connection创建与之后的statement释放不再抽取之列.
StatementHandler在框架中也分为二级:第一级为一个抽象类,主要完成一些基础性工作,如规范创建流程,超时等设置;
第二级主要就是实现上述的1,2,3三个主要操作,第二级的实现类主要分为Simple,Prepared,Callable,具体创建使用哪一个对象
是由Statement配置的statementType有关系,没有配置默认是Prepared
上述的三个操作可以说是jdbc操作最复杂,最繁琐的地方,尤其是后两个
设置参数与执行语句后获取结果,可以说这是要所有持久化框架必须要面临的一个问题,这里使用两个接口来抽象这个变化点
ParameterHandler与ResultSetHandler其中一个主要做参数设置,而另一个主要做结果提取,它们可以算得上是下一个层次
到此处有必须分析一下executor与StatementHandler搭配关系.executor主要有:SimpleExecutor,ReuseExecutor,BatchExecutor,CachingExecutor
其中最主要的是前三个;而对于StatementHandler主要有:SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler,
RoutingStatementHandler,其中最主要的也是前三个,从理论上讲它们之间是没有严格的约束关系的,它们是相容的,但实际它们之间的搭配关系也不是随便的
具体分析一下:
SimpleExecutor:是一个中规中矩的执行器,没有什么特别的功能,它实现的功能就是,委托StatementHandler来完成操作,例如更新,委托StatementHandler
设置参数,委托StatementHandler执行更新;同样查询,也是委托StatementHandler设置参数,委托StatementHandler执行查询.但是这个执行器不支持
doFlushStatements方法,它仅仅只返回一个空的列表,此方法主要是为批量执行器BatchExecutor预备的
BatchExecutor:是一个批量执行器,它主要着眼于更新,即批量的更新,这是它最大的作用,要实现这个功能必须要两个方法联合使用,即更新与doFlushStatements
更新doUpdate方法主要负责添加批量的语句与设置参数,而doFlushStatements主要负责一次所有操作,而对于程序员来说doFlushStatements是不可见的,因为
它会在session层进行事物提交时进行自动的执行此方法,以完成批量方法的实际执行,session实际也是委托BatchExecutor的commit方法来调用doFlushStatements
至于查询BatchExecutor与SimpleExecutor实际基本相同,只是BatchExecutor在执行查询前会先执行一次批量更新doFlushStatements,其它都一样
ReuseExecutor:被称为重复使用执行器,这个执行器与SimpleExecutor非常的相似,它唯一与SimpleExecutor不同的它缓存Statement对象,并对重复出现的SQL使用
缓存的Statement对象,前提是session还没有关闭.
CachingExecutor:暂时还不能体会它含义,只知道它是对一种执行器进行的缓存装饰,具体在什么情况下使用还不清楚
=================================================================================================
SimpleStatementHandler:简单语句处理器,此处理器只用来处理不带占位符参数的语句,这是它最大的特点,因为它设置参数方法不做任何情
它可以和各种类型的Executor进行搭配
PreparedStatementHandler:参数语句处理器,这是最常见的处理器,也是默认的语句处理器,它可以和各种类型的Executor进行搭配
CallableStatementHandler:存储过程语句处理器,这个处理器也只有调用存储过程时才用得上,其实它也可以与各种类型的Executor进行搭配,
但我认为最还是与简单类型执行器进行搭配,
综合上面的分析,在执行器当中,如果没有特别的理由我觉得就使用SimpleExecutor,如果需要批量更新,必须使用BatchExecutor
在语句处理器当中,如果没有参数可以使用SimpleStatementHandler,当然也可使用PreparedStatementHandler,如果是存在过程
语句必须使用CallableStatementHandler,其它情况使用PreparedStatementHandler,也就是说
SimpleExecutor+PreparedStatementHandler是最常见的搭配
还有一点需要注意的就是执行器是程序员可以临时选择的,而语句处理器是事先配置好,不可在程序进行更改,也就是语句处理器受语句类型约束
可以事先确定,而执行器的选择需要根据实际执行业务的情况进行选择,总体来说如果不进行大量数据批量执行一般都需要选择SimpleExecutor,
它也是iBatis的默认执行器选择,最最重要的一点是无论选择是哪一种如果全局配置是可缓存的,则无论哪一种执行器都会被CachingExecutor进行
装饰这一点一定要清楚
D.ParameterHandler与ResultSetHandler
先说ParameterHandler,它主要是为statement参数设置而设计,作为上层的调用通常只会传递一个statement进来,那么一个处理器
是如何完成参数设置的呢?默认的参数处理器是这样的处理的,一个ParameterHandler通常会持有一个boundSql,而在boundSql中
有当前要执行的sql语句的参数映射列表:List<ParameterMapping> parameterMappings,这个列表记录当前执行操作每个参数
设置的细节,列表中的一个元素,完成一个参数的设置.此处有一个很重要的问题List<ParameterMapping>这个列表是从何处而来,又是如何
与此操作联系在一起的,下面将进行详细的分析:
首先看一看ParameterHandler是如何来的,它是由BaseStaementHandler在构造方法中创建的,创建代码如下:
this.boundSql = mappedStatement.getBoundSql(parameterObject);
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowOffset, rowLimit, parameterHandler, resultHandler, boundSql);
从上面的代码你可以看出boundSql是由"映射语句"创建的,而ParameterHandler与ResultSetHandler都是由配置对象创建的
问题的焦点现在变成了"映射语句"是如何创建一个BoundSql的?因为每一个参数的设置细节是由此对象来完成的,而往往在一变化的
sql操作中参数的个数有可能事先并不确定,那么"映射语句"是如何来解决这件事情的呢?
再来看一看MappedStatement,MappedStatement是一个独立的类型,它没有继承其它类,与没有实现其它接口,这个类保留了非常详细的信息
如:语句配置时的id,资源地址,配置对象,fetchSize,timeout,statementType,resultSetType,cache,sqlSource,parameterMap
resultMaps等参数,其中一个参数是创建BoundSql的关键,这个参数是SqlSource,这个参数是一个接口,其源代码如下:
/**
* 一个sql源,总是可以通过一个提供参数的对象,来提供一个被绑定的sql
*
* @author ZXC
*
*/
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
可以看到这个接口是专门为抽取BoundSql的变化而设计,每一个"已映射语句"对象都会有一个SqlSource,这是一个非常合理的设计.在"已映射语句"对象
中获取BoundSql的逻辑的为:
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.size() <= 0) {
boundSql = new BoundSql(boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
return boundSql;
}
现在继续向下进行分析,"已映射语句"中的sqlSource与parameterMap是从哪里来的?
它是通过辅助类MapperBuilderAssistant的辅助方法:
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap,
Class parameterType, String resultMap, Class resultType, ResultSetType resultSetType,
boolean flushCache, boolean useCache, KeyGenerator keyGenerator, String keyProperty)
创建获取的,其中传递的参数有SqlSource与parameterMap,这是对参数设置起决定作用的两个重要参数,它们两个都可以提供参数设置的细节
即它们都有可能提供一个参数映射列表:List<ParameterMapping> parameterMappings,并且从代码中可以看出,只有在SqlSource提供
的boundSql没有List<ParameterMapping> parameterMappings时才会使用parameterMap提供的getParameterMappings来重新
构建一个BoundSql返回,那么在什么样情况下BoundSql提供的parameterMappings会不存在呢?现在我还不知道,但是我推测应该是没有使用
动态标记#{XX}的那些语句,一旦使用这个标记,总会生成parameterMappings,如果没有使用动态标记,但是使用了?我想一定要手工配置一个parameterMap
否则肯定会报异常,也就是说要不就全部配置?占位符,然后配置一个parameterMap;要么就全部配置动态占位符#{XX}而parameterMap将不再起作用
从上面辅助方法可以看parameterMap参数此时还是一个String类型的参数,而当一个MappedStatement对象创建后它就变成了一个ParameterMap
对象了,那这是如何变化的呢?
在辅助类中有一个方法setStatementParameterMap(String parameterMap, Class parameterTypeClass, MappedStatement.Builder statementBuilder)
主要就是来完成这个工作的,从这里可以看到这个方法除了第三个参数构建器之外还有两个参数parameterMap与parameterTypeClass,此时可以联想
到ibatis的配置文件,我想这两个参数应该是与映射文件配置语句听参数是对应的,这两个参数是有优先级的,如果parameterMap配置了,则parameterTypeClass就不会起作用
如果没有配置parameterMap则会使用parameterTypeClass创建一个空的parameterMap即其中的映射项List<ParameterMapping> parameterMappings是一个空列表
到此为止基本已经说清了parameterMap的形成过程了,下面继续看SqlSource来源
正常的sqlSource产生的途径主要在XMLStatementBuilder中的parseStatementNode方法,它主要用来解析"已映射语句"在配置文件
中对应的结点配置,最终也将会在此方法中产生一个sqlSource对象,详细分析可以查看XMLStatementBuilder中的相关注释以及TypeHandler接口中的注释.最后得出一个
比较粗糙的结果就是:sqlSource可以通过动态计算,来提供一个BoundSql,而BoundSql可以提供一个List<ParameterMapping> parameterMappings,
在一个"已映射语句"中sqlSource是稳定的,也就是它是被缓存的,但是由它产生的BoundSql是一即时对象,即每次操作都产生一个新的BoundSql,
当获取一个List<ParameterMapping> parameterMappings之后要设置参数就非常的简单了,只需要委托TypeHandler就可以完成相应的参数设置工作
//===================================================================================
看完成参数设置处理,接下来看一看结果集的处理在框架是如何抽象的.在TypeHandler接口的分析中(可以参考其中的文档注释),我们可以知道
TypeHandler从一个低层次上既抽象单个字段的参数设置,也抽象了单个字段结果提取,由于级别低所相对操作就比较繁琐,框架在一个较高的层次
进行了进一步的抽象参数设置即是前面分析的ParameterHandler,而结果提取就是ResultSetHandler,下面就具体看框架是如何抽象结果处理的
与ParameterHandler一样,ResultSetHandler也是从BaseStatementHandler创建而来的:
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowOffset, rowLimit, parameterHandler, resultHandler, boundSql);
一个StatementHandler通常做得事情很结构化,也很简单,实际复杂的事情都委托参数处理器与结果处理器去完成了,比如当前一个statement执行查询
后,要提取结果,其中一种StatementHandler实现的代码为:
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
现在我们就具体分析一下resultSetHandler究竟是如何完成结果处理的,查看框架的ResultSetHandler的默认实现,从整个代码上看,结果处理或者说
结果提取,要比较参数设置要复杂许多,两者是各有特点,参数设置是对象的创建非常复杂,而实际参数设置简单,因为参数动态性较大;而结果提取是对象创建
相对简单,而结果处理的本身相对复杂.通过分析可以发现,默认的ResultSetHandler进行了一些相对比较高级的处理,其中一个比较繁琐的地方是关联查询
我们先暂时将这个比较复杂的地方去除,整个流程还是比较清楚的:
第一步是将多结果集的问题转换成单一结果集问题(handleResults),在解决这个问题的时候有一个选择ResultHandler的问题,ResultHandler与ResultSetHandler不是同
一回事,ResultSetHandler是处理整个Statement,而ResultHandler处理的是一个记录行映射之后的结果上下文,代码为:void handleResult(ResultContext context);
第二步在单一结果集中处理中首先处理了分页逻辑后,接着将问题的核心从处理多条记录转向了处理一条记录(loadResultObject);
第三步在处理一条记录的逻辑中主要解决了一条记录对象中某个关联对象的延迟加载,以及连接查询,但其中最核心的逻辑留给了下一级的调用处理(mapResults);
第四步是一个纯粹的结果映射,不带有任何的其它逻辑,这也是我们当前最关心的问题,在结果映射中主要分两个比较重要的逻辑,就是以结果对象的类型是否为Map型有
了个比较明显的界限,其中的一个细节是结果对象是否存在一个columnLabel属性,这里的columnLabel是数据库列字段的大写形式,如果存在则会在自动结果映射中添加
一个这样的映射对象,而在Map型对象在进行元信息包装的时候,它们默认是具有任何属性的.
通过上面的分析基本上可以确定,如果在配置中没有指定的一个ResultMap,但只要映射对象是一个Map类型,仍然可以获取完整的数据.
接下来我们需要分析一下从外部配置的ResultMap产生的途径:
一个ResultMap总是跟随着一个"已映射语句"的,一个ResultMap在一个"已映射语句"中表现为一个列表:private List<ResultMap>resultMaps;
这是因为一个"已映射语句"在执行完成之后有可能会产生多个结果集,而第一个结果集,需要一个ResultMap,那么这个结果集列表是如何得来的呢?
在构建一个MappedStatement同时也会构建一个ResultMap的列表,下面是在MapperBuilderAssistant类中一段相关的代码
private void setStatementResultMap(String resultMap, Class resultType, ResultSetType resultSetType, MappedStatement.Builder statementBuilder) {
resultMap = applyCurrentNamespace(resultMap);
List<ResultMap> resultMaps = new ArrayList<ResultMap>();
if (resultMap != null) {
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
}
} else if (resultType != null) {
ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<ResultMapping>());
resultMaps.add(inlineResultMapBuilder.build());
}
statementBuilder.resultMaps(resultMaps);
statementBuilder.resultSetType(resultSetType);
}
从上面的代码可以看出,如果配置过resultMap则会使用配置的结果映射,如果没有配置,则退而求其次如果配置了resultType,则会使用此对象创建一个
内建的ResultMap对象,但是此对象有一个问题就是它的映射字段列表为一个空列表.如果两个都没有配置则将不会有任何的ResultMap存在,这是没有意义的
我觉得程序应该在这种情况下抛出异常(这里只所没有抛出异常我推断是因为在配置文件,type是一个必须填写的属性).
接着我们需要来看一下如果只配置resultType框架是如何解决这种信息不全的问题的,这其中的秘密就是对象的元信息包装
metaResultObject.findProperty(columnLabel);在元信息包装的时候,元信息类记录一个完全大写的虚属性到实际属性的映射的关系
这也是在使用findProperty方法而参数是一个完全大写的columnLabel时也能够正确获取一个属性名称的秘密,下面是在Reflector类中的一段代码:
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(), propName);
}
可以看到caseInsensitivePropertyMap会保存着一个完全大写到真实属性的一个映射关系,这在一般情况不会有太大的问题,它的确找到了一个从数据列字段
名称到属性名称间对应的途径,这可以使我们自动建立一个映射,而且还可以正常使用,至于属性冲突的问题,只要在设计时稍加注意就可以避免这样的问题,退一步
说,就是算是有冲突也没有太大的关系,因为手动配置优先于,自动映射,所以即使发生冲突我们也可以通常手工配置来解决这个问题
//==================================================================
以上基本上从最高层到最低层进行了一个很基本的分析,对于一些高级的功能并没有考虑太多,但是对于设置设置与结果映射应该
说做了相对深入的分析,至于其它的一些高级功能在以有时间在进行分析,或在用得着的时候再去看它
//==================================================================
以上几个重要的层,session的创建不Configuration对象创建的,其它几个对象都是由Configuration
对象通过对应的new前缀方法进行创建的
分享到:
相关推荐
本篇文章基于“Ibatis3手册 Ibatis3参考手册”的标题及描述,深入解析Ibatis3的核心概念、架构特点以及如何进行实际操作,旨在帮助读者全面理解Ibatis3的工作原理与应用场景。 ### 一、Ibatis3简介 Ibatis3是一款...
**深入分析 iBATIS 框架之系统架构与映射原理** iBATIS 是一个优秀的持久层框架,它允许开发者将 SQL 语句与 Java 代码分离,从而简化了数据库访问层的开发工作。本篇文章将深入探讨 iBATIS 的核心系统架构以及其...
一、iBatis架构概述 iBatis主要由SqlMapConfig.xml配置文件、SqlMap接口和Executor执行器三大部分构成。SqlMapConfig.xml配置文件定义了数据源、事务管理器等全局设置;SqlMap接口则封装了SQL语句,提供了数据操作的...
Spring3、Struts2和Ibatis的整合,构建了一个完整的MVC+持久层架构。Spring作为整个应用的调度中心,管理所有对象的生命周期,包括Struts2的Action和Ibatis的SqlSession。Struts2负责接收HTTP请求,调用Action执行...
内容简介 《iBATIS实战》是讲述iBATIS框架的...项目经理、数据库管理员、质量保证员与测试员以及系统分析师也能从《iBATIS实战》中受益。 作者简介 作者:(加拿大)Clinton Begin (加拿大)Brandon Goodin 译者:叶俊
**标题与描述解析** 标题"ibatis源码"指出我们关注的是开源的Java持久层框架iBATIS的源代码。这个框架在Java开发中广泛使用,尤其在处理数据库...通过对这些文件的分析,可以更全面地理解iBATIS框架的架构和实现细节。
Ibatis框架是一个轻量级的Java持久层框架,它...通过分析源码,开发者能够深入理解Ibatis如何处理数据访问,如何与Web层交互,以及如何组织和管理数据库操作。同时,这样的分层结构也有助于代码的可维护性和扩展性。
- iBATIS的基本架构和设计理念,如SQL映射的概念、DAO(数据访问对象)的设计模式。 - 如何在项目中集成iBATIS,包括添加依赖、配置数据源和SqlSessionFactory。 - SQL Map XML文件的结构和语法,如设置SQL语句、...
iBATIS实战 iBATIS In Action PDF...本书既可为广大的开发人员(不仅仅是Web应用程序开发人员)提供指导,也可为架构师的项目决策提供参考。项目经理、数据库管理员、质量保证员与测试员以及系统分析师也能从本书中受益。
《iBATIS-SqlMaps》则可能更侧重于实战和案例分析,通过具体的项目场景来展示如何设计和实施iBATIS解决方案,以及如何利用iBATIS实现更高效的数据操作。 两本书结合阅读,开发者可以从理论到实践全面掌握iBATIS框架...
3. **数据访问层(DAL层)**:DAL层主要负责与数据库的交互,它使用Ibatis作为数据映射框架,将数据库操作抽象成SQL映射文件,实现了数据库操作与具体SQL语句的解耦。Ibatis允许开发者在XML配置文件中编写SQL语句,...
3. **参数映射**:Ibatis支持简单类型和复杂对象作为参数,通过`@Param`注解或`<param>`标签进行映射。 4. **结果映射**:通过`@ResultMap`或XML中的`<resultMap>`定义,Ibatis可以自动将查询结果转换为Java对象,...
iBATIS实战 iBATIS In Action PDF...本书既可为广大的开发人员(不仅仅是Web应用程序开发人员)提供指导,也可为架构师的项目决策提供参考。项目经理、数据库管理员、质量保证员与测试员以及系统分析师也能从本书中受益。
iBATIS实战 iBATIS In Action PDF...本书既可为广大的开发人员(不仅仅是Web应用程序开发人员)提供指导,也可为架构师的项目决策提供参考。项目经理、数据库管理员、质量保证员与测试员以及系统分析师也能从本书中受益。
通过对源码的深入分析,我们可以理解ibatis的核心机制,掌握数据库操作的底层原理,从而更好地利用和优化这个强大的持久层框架。在这个压缩包中,包含的文件是《ibatis框架源码剖析》的全文内容。 ibatis作为一个轻...
3. **iBatis2**:iBatis是一个SQL映射框架,它将SQL语句与Java代码分离,提高了代码的可维护性。在iBatis中,日志可以帮助我们跟踪SQL执行情况,包括查询时间、返回结果等,这对于性能调优至关重要。可以通过配置log...
Struts、Spring 和 iBatis 是 Java Web 开发中三个非常重要的开源框架,它们共同构建了一个灵活、可扩展且易于维护的系统架构。这个"Struts+Spring+Ibatis示例"提供了一个基础的整合应用,帮助开发者理解这三者如何...
3. **OSGi与iBatis的集成**:了解如何在OSGi中导入iBatis的依赖,并配置服务以供其他模块使用。可能涉及到Bundle的导出和导入、服务的注册与查找等。 4. **RAP框架**:如果`lggege.rap.demo.server`确实与RAP相关,...
首先,我们来了解一下iBATIS的基本架构。iBATIS由四大核心部分组成:SqlMapConfig.xml配置文件、SqlMap接口、SQL映射文件和数据源。SqlMapConfig.xml是全局配置文件,包含了数据源、事务管理器等信息。SqlMap接口...