示例代码

之前的文章说过,对于MyBatis来说insert、update、delete是一组的,因为对于MyBatis来说它们都是update;select是一组的,因为对于MyBatis来说它就是select。

本文研究一下select的实现流程,示例代码为:

 public void testSelectOne() {
System.out.println(mailDao.selectMailById(8));
}

selectMailById方法的实现为:

 public Mail selectMailById(long id) {
SqlSession ss = ssf.openSession();
try {
return ss.selectOne(NAME_SPACE + "selectMailById", id);
} finally {
ss.close();
}
}

我们知道MyBatis提供的select有selectList和selectOne两个方法,但是本文只分析且只需要分析selectOne方法,原因后面说。

selectOne方法流程

先看一下SqlSession的selectOne方法流程,方法位于DefaultSqlSession中:

 public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

这里就是为什么我说selectOne与selectList两个方法只需要分析selectList方法就可以了的原因,因为在MyBatis中所有selectOne操作最后都会转换为selectList操作,无非就是操作完毕之后判断一下结果集的个数,如果结果集个数超过一个就报错。

接着看下第3行的selectList的代码实现,方法同样位于DefaultSqlSession中:

 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

第3行获取MappedStatement就不说了,跟一下第4行Executor的query方法实现,这里使用了一个装饰器模式,给SimpleExecutor加上了缓存功能,代码位于CachingExecutor中:

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

第2行的代码获取BoundSql,BoundSql中的内容上文已经说过了,最后也会有总结。

第3行的代码根据输入参数构建缓存Key。

第4行的代码执行查询操作,看下代码实现,代码同样位于CachingExecutor中:

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

这里并没有配置且引用Cache,因此不执行第4行的判断,执行第17行的代码,代码位于SimpleExecutor的父类BaseExecutor中,源码实现为:

 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

这里执行第16行的代码,queryFromDatabase方法实现为:

 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

代码走到第5行,最终执行duQuery方法,方法的实现为:

 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行~第6行的代码都和前文update是一样的,就不说了,handler有印象的朋友应该记得是PreparedStatementHandler,下一部分就分析一下和update的区别,PreparedStatementHandler的query方法是如何实现的。

PreparedStatementHandler的query方法实现

跟一下PreparedStatementHandler的query方法跟到底,其最终实现为:

 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}

看到第3行执行查询操作,第4行的代码处理结果集,将结果集转换为List,handleResultSets方法实现为:

 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);
}

总结一下这个方法。

第7行代码,通过PreparedStatement的getResultSet方法获取ResultSet,并将ResultSet包装为ResultSetWrapper,ResultSetWrapper除了包含了ResultSet之外,还依次定义了数据库返回的每条数据的每行列名、列对应的JDBC类型、列对应的Java Class的类型,除此之外最主要的是还包含了TypeHandlerRegister(类型处理器,所有的参数都是通过TypeHandler进行设置的)。

第9行代码,获取该<select>标签中定义的ResultMap,不过这里我有点没弄明白,一个<select>标签按道理应该只能定义一个resultMap属性,但是这里却获取的是一个List<ResultMap>,不是很清楚。

第11行代码,做了一个校验,即如果select出来有结果返回,但是没有ResultMap或者ResultType与之对应的话,抛出异常,道理很简单,没有这2者之一,MyBatis并不知道将返回转成什么样子。

第12行~第18行的代码,将ResultSetWrapper中的值根据ResultMap,转成Java对象,先存储在multipleResults中,这是一个List<Object>。

第20行~第33行的代码,是用于处理<select>中定义的resultSets的,由于这里没有定义,因此跳过。

第35行的代码,将multipleResults,根据其size大小,如果size=1,获取0号元素,强转为List<Object>;如果size!=1,直接返回multipleResults。

总得来说这个方法,根据数据库返回的结果,封装为自定义的ResultMap的流程基本是没问题的,只是这里的一个问题是,为什么要定义一个multipleResults,最后根据multipleResults的size来判断并拆分最终的结果,还没有完全搞懂,这部分还要留待后面的工作中随着MyBatis应用的深入再去学习。

小结

前文已经对MyBatis配置文件加载、CRUD操作都进行了分析,就从我自己的感觉来说,对整个流程基本有数,但是很多地方感觉还是有些印象不深,最主要的就是从什么地方获取什么数据,获取的数据在什么地方使用,因此这里做一个总结加深印象,主要总结的是MyBatis中重点的类中持有哪些内容。

首先是SqlSessionFactory,默认使用的是DefaultSqlSessionFactory,我们使用它来每次打开一个SqlSession,SqlSessionFactory持有:

接着是Configuration,它是所有配置信息最终存储的位置,其中大部分的属性尤其是布尔型值都可以通过<settings>标签进行配置,任何的操作(如打开一个SqlSession、执行增删改查等)都要从Configuration中拿相关信息,Configuration持有的一些重要属性有:

接着是Environment,它存储的是配置的数据库环境信息,可以指定多个,但是最终只能使用一个,Environment持有的一些重要属性有:

接着是MappedStatement,一个MappedStatement对应mapper文件中的一个<insert>、<delete>、<update>、<select>,每次执行MyBatis操作的时候先获取对应的MappedStatement,MappedStatement持有的一些重要属性有:

接着是BoundSql,BoundSql中最重要存储的就是当前要执行的SQL语句,其余还有要设置的参数信息与参数对象,BoundSql持有的属性有:

最后是ParameterMapping,ParameterMapping是待设置的参数映射,存储了待设置的参数的相关信息,ParameterMapping持有的属性有:

MyBatis中使用到的设计模式

下面来总结一下MyBatis中使用到的设计模式,有些设计模式可能在到目前位置的文章中没有体现,但是在之后的【MyBatis源码分析】系列文章中也会体现,这里一并先列举出来:

1、建造者模式

代码示例为SqlSessionFactoryBuilder,代码片段:

 public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
} public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
} public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
} public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

重载了大量的build方法,可以根据参数的不同构建出不同的SqlSessionFactory。

2、抽象工厂模式

代码示例为TransactionFactory,代码片段为:

 public class JdbcTransactionFactory implements TransactionFactory {

   @Override
public void setProperties(Properties props) {
} @Override
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
} @Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}

抽象出事物工厂,不同的事物类型实现不同的事物工厂,像这里就是Jdbc事物工厂,通过Jdbc事物工厂去返回事物接口的具体实现。

其它的像DataSourceFactory也是抽象工厂模式的实现。

3、模板模式

代码示例为BaseExecutor,代码片段:

 protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException; protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
throws SQLException; protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException; protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;

BaseExecutor封装好方法流程,子类例如SimpleExecutor去实现。

4、责任链模式

代码示例为InterceptorChain,代码片段为:

 public class InterceptorChain {

   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

   public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
} public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
} public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
} }

可以根据需要添加自己的Interceptor,最终按照定义的Interceptor的顺序逐一嵌套执行。

5、装饰器模式

代码示例为CachingExecutor,代码片段为:

 public class CachingExecutor implements Executor {

   private Executor delegate;
private TransactionalCacheManager tcm = new TransactionalCacheManager(); public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
} ...
}

给Executor添加上了缓存的功能,update与query的时候会根据用户配置先尝试操作缓存。

在MyBatis中还有很多地方使用到了装饰器模式,例如StatementHandler、Cache。

6、代理模式

代码示例为PooledConnection,代码片段为:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}

这层代理的作用主要是为了让Connection使用完毕之后从栈中弹出来。

MyBatis中的插件也是使用代理模式实现的,这个在后面会说到。

【MyBatis源码分析】select源码分析及小结的更多相关文章

  1. MyBatis框架的使用及源码分析(九) Executor

    从<MyBatis框架的使用及源码分析(八) MapperMethod>文中我们知道执行Mapper的每一个接口方法,最后调用的是MapperMethod.execute方法.而当执行Ma ...

  2. select源码分析(linux2.6.11)

    本文以tcp poll为例子来分析select的源码,下面是函数调用顺序.select--->sys_select->do_select--->sock_poll--->tcp ...

  3. MyBatis框架的使用及源码分析(十一) StatementHandler

    我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Ex ...

  4. MyBatis框架的使用及源码分析(一) 配置与使用

    我们先来看一个例子,简单的了解一下mybatis的mapper接口方式的使用. package org.mybatis.spring.sample; import org.apache.ibatis. ...

  5. 手机自动化测试:Appium源码分析之跟踪代码分析七

    手机自动化测试:Appium源码分析之跟踪代码分析七   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.poptest推出手机自 ...

  6. Activiti架构分析及源码详解

    目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...

  7. kafka源码分析之一server启动分析

    0. 关键概念 关键概念 Concepts Function Topic 用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上. Partition 是Kafka中横向扩展和一 ...

  8. [ASP.NET]分析MVC5源码,并实现一个ASP.MVC

    本节内容不是MVC入门教程,主要讲MVC原理,实现一个和ASP.NET MVC类似基本原理的项目. MVC原理是依赖于ASP.NET管道事件基础之上的.对于这块,可阅读上节内容 [ASP.NET]谈谈 ...

  9. Android源码分析--CircleImageView 源码详解

    源码地址为 https://github.com/hdodenhof/CircleImageView 实际上就是一个圆形的imageview 的自定义控件.代码写的很优雅,实现效果也很好, 特此分析. ...

随机推荐

  1. CSS预处理框架:less,scss

    CSS预处理器:less和sass:CSS 预处理器是一种语言用来为 CSS 增加一些编程的的特性,无需考虑浏览器的兼容性问题,例如你可以在 CSS 中使用 变量.简单的程序逻辑.函数等等在编程语言中 ...

  2. Sublime Text 2 (for OS X )配置成可以运行基于python3解释器的 .py文件

    Mac自带的python 其version是python 2.7 官网下的Sublime Text 2部署好了以后默认也是 为了使ST2 可以在command+B时可以运行基于python3的.py, ...

  3. VS打包资源文件,转自推酷,请小星同学查看

    上篇博客把收费系统的总体设计进行了一遍讲解,讲解的同时掺杂了些有关.NET编译机制的总结.程序编写测试完成后接下来我们要做的是打包部署程序,但VS2012让人心痛的是没有了打包工具.不知道出于什么原因 ...

  4. HDU - 2276 Kiki &amp;amp; Little Kiki 2

    Description There are n lights in a circle numbered from 1 to n. The left of light 1 is light n, and ...

  5. 跨平台移动框架iMAG开发入门

    iMAG是一个非常简洁高效的移动跨平台开发框架,开发一次能够同一时候兼容Android和iOS平台,有点儿Web开发基础就能非常快上手.当前移动端跨平台开发的框架有非常多,但用iMAG另一个优点,就是 ...

  6. leetcode每日刷题计划-简单篇day12

    Num 125 验证回文串 Valid Palindrome 非常有收货的一道题嘻嘻嘻,本来是考试期间划水挑的题,坑点有点多 第一个是注意对temp1和temp2中途更新的判断 第二个是字符串频繁的作 ...

  7. java.lang.IllegalArgumentException: An invalid domain [.test.com] was specified for this cookie

    https://blog.csdn.net/cml_blog/article/details/52135115 当项目中使用单点登录功能时,通常会使用cookie进行信息的保存,这样就可以在多个子域名 ...

  8. hive外部表删除遇到的一个坑

    hive外部表删除遇到的一个坑 操作步骤 创建某个表(create external table xxx location xxx) 插入数据(insert xxx select xxx from x ...

  9. LeetCode: Largest Rectangle in Histogram 解题报告

    Largest Rectangle in Histogram Given n non-negative integers representing the histogram's bar height ...

  10. Win2003+apache+PHP+SqlServer2008 配置

    Win2003+apache+PHP+SqlServer2008 配置     安装前的准备: 1.Apache2.2.2 2.PHP5.2.17 3.SqlServer2008 4.sqlncli. ...