【MyBatis源码分析】select源码分析及小结

示例代码

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

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

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

selectMailById方法的实现为:

1 public Mail selectMailById(long id) {
2     SqlSession ss = ssf.openSession();
3     try {
4         return ss.selectOne(NAME_SPACE + "selectMailById", id);
5     } finally {
6         ss.close();
7     }
8 }

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

selectOne方法流程

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

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

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

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

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

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

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

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

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

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

 1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
 2       throws SQLException {
 3     Cache cache = ms.getCache();
 4     if (cache != null) {
 5       flushCacheIfRequired(ms);
 6       if (ms.isUseCache() && resultHandler == null) {
 7         ensureNoOutParams(ms, parameterObject, boundSql);
 8         @SuppressWarnings("unchecked")
 9         List<E> list = (List<E>) tcm.getObject(cache, key);
10         if (list == null) {
11           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
12           tcm.putObject(cache, key, list); // issue #578 and #116
13         }
14         return list;
15       }
16     }
17     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
18 }

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

 1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 2     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
 3     if (closed) {
 4       throw new ExecutorException("Executor was closed.");
 5     }
 6     if (queryStack == 0 && ms.isFlushCacheRequired()) {
 7       clearLocalCache();
 8     }
 9     List<E> list;
10     try {
11       queryStack++;
12       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
13       if (list != null) {
14         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
15       } else {
16         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
17       }
18     } finally {
19       queryStack--;
20     }
21     if (queryStack == 0) {
22       for (DeferredLoad deferredLoad : deferredLoads) {
23         deferredLoad.load();
24       }
25       // issue #601
26       deferredLoads.clear();
27       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
28         // issue #482
29         clearLocalCache();
30       }
31     }
32     return list;
33 }

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

 1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 2     List<E> list;
 3     localCache.putObject(key, EXECUTION_PLACEHOLDER);
 4     try {
 5       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
 6     } finally {
 7       localCache.removeObject(key);
 8     }
 9     localCache.putObject(key, list);
10     if (ms.getStatementType() == StatementType.CALLABLE) {
11       localOutputParameterCache.putObject(key, parameter);
12     }
13     return list;
14 }

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

 1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.<E>query(stmt, resultHandler);
 8     } finally {
 9       closeStatement(stmt);
10     }
11 }

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

PreparedStatementHandler的query方法实现

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

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

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

 1 public List<Object> handleResultSets(Statement stmt) throws SQLException {
 2     ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
 3
 4     final List<Object> multipleResults = new ArrayList<Object>();
 5
 6     int resultSetCount = 0;
 7     ResultSetWrapper rsw = getFirstResultSet(stmt);
 8
 9     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
10     int resultMapCount = resultMaps.size();
11     validateResultMapsCount(rsw, resultMapCount);
12     while (rsw != null && resultMapCount > resultSetCount) {
13       ResultMap resultMap = resultMaps.get(resultSetCount);
14       handleResultSet(rsw, resultMap, multipleResults, null);
15       rsw = getNextResultSet(stmt);
16       cleanUpAfterHandlingResultSet();
17       resultSetCount++;
18     }
19
20     String[] resultSets = mappedStatement.getResultSets();
21     if (resultSets != null) {
22       while (rsw != null && resultSetCount < resultSets.length) {
23         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
24         if (parentMapping != null) {
25           String nestedResultMapId = parentMapping.getNestedResultMapId();
26           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
27           handleResultSet(rsw, resultMap, null, parentMapping);
28         }
29         rsw = getNextResultSet(stmt);
30         cleanUpAfterHandlingResultSet();
31         resultSetCount++;
32       }
33     }
34
35     return collapseSingleResultList(multipleResults);
36 }

总结一下这个方法。

第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,代码片段:

 1 public SqlSessionFactory build(Reader reader) {
 2     return build(reader, null, null);
 3   }
 4
 5   public SqlSessionFactory build(Reader reader, String environment) {
 6     return build(reader, environment, null);
 7   }
 8
 9   public SqlSessionFactory build(Reader reader, Properties properties) {
10     return build(reader, null, properties);
11   }
12
13   public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
14     try {
15       XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
16       return build(parser.parse());
17     } catch (Exception e) {
18       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
19     } finally {
20       ErrorContext.instance().reset();
21       try {
22         reader.close();
23       } catch (IOException e) {
24         // Intentionally ignore. Prefer previous error.
25       }
26     }
27   }

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

2、抽象工厂模式

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

 1 public class JdbcTransactionFactory implements TransactionFactory {
 2
 3   @Override
 4   public void setProperties(Properties props) {
 5   }
 6
 7   @Override
 8   public Transaction newTransaction(Connection conn) {
 9     return new JdbcTransaction(conn);
10   }
11
12   @Override
13   public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
14     return new JdbcTransaction(ds, level, autoCommit);
15   }
16 }

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

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

3、模板模式

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

 1 protected abstract int doUpdate(MappedStatement ms, Object parameter)
 2       throws SQLException;
 3
 4 protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
 5       throws SQLException;
 6
 7 protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
 8       throws SQLException;
 9
10 protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
11       throws SQLException;

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

4、责任链模式

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

 1 public class InterceptorChain {
 2
 3   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
 4
 5   public Object pluginAll(Object target) {
 6     for (Interceptor interceptor : interceptors) {
 7       target = interceptor.plugin(target);
 8     }
 9     return target;
10   }
11
12   public void addInterceptor(Interceptor interceptor) {
13     interceptors.add(interceptor);
14   }
15
16   public List<Interceptor> getInterceptors() {
17     return Collections.unmodifiableList(interceptors);
18   }
19
20 }

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

5、装饰器模式

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

 1 public class CachingExecutor implements Executor {
 2
 3   private Executor delegate;
 4   private TransactionalCacheManager tcm = new TransactionalCacheManager();
 5
 6   public CachingExecutor(Executor delegate) {
 7     this.delegate = delegate;
 8     delegate.setExecutorWrapper(this);
 9   }
10
11   ...
12 }

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

6、代理模式

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

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2     String methodName = method.getName();
 3     if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
 4       dataSource.pushConnection(this);
 5       return null;
 6     } else {
 7       try {
 8         if (!Object.class.equals(method.getDeclaringClass())) {
 9           // issue #579 toString() should never fail
10           // throw an SQLException instead of a Runtime
11           checkConnection();
12         }
13         return method.invoke(realConnection, args);
14       } catch (Throwable t) {
15         throw ExceptionUtil.unwrapThrowable(t);
16       }
17     }
18 }

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

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

时间: 2024-08-10 21:27:44

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

转 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)

深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇) 最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前九篇中,介绍了mybatis的配置以及使用, 那么本篇将走进mybatis的源码,分析mybatis 的执行流程, 好啦,鄙人不喜欢口水话,还是直接上干活吧: 1. SqlSessionFactory 与 SqlSession. 通过前面的章节对于mybatis 的介绍及使用,大家都能体会到SqlSession的重要性了吧, 没错,从表面上来看,

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

我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Excecutor执行sql操作的最终都调用了StatementHandler 来执行,我们拿SimpleExecutor来看: public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement st

HBase1.0.0源码分析之请求处理流程分析以Put操作为例(二)

HBase1.0.0源码分析之请求处理流程分析以Put操作为例(二) 1.通过mutate(put)操作,将单个put操作添加到缓冲操作中,这些缓冲操作其实就是Put的父类的一个List的集合.如下: private List<Row> writeAsyncBuffer = new LinkedList<>(); writeAsyncBuffer.add(m); 当writeAsyncBuffer满了之后或者是人为的调用backgroundFlushCommits操作促使缓冲池中的

OpenStack_Swift源码分析——Object-auditor源码分析(2)

1 Object-aduitor审计具体分析 上一篇文章中,讲解了Object-aduitor的启动,其中审计的具体执行是AuditorWorker实现的,在run_audit中实例化了AuditorWorker类,并调用audit_all_objects方法,下面看此方法的具体代码实现: def audit_all_objects(self, mode='once', device_dirs=None): #run_forever传过来的mode 为forever description =

OpenStack_Swift源码分析——Object-auditor源码分析(1)

1 Object-auditor 的启动 Object-auditor的启动和object-replicator的启动过程是一样的,首先是执行启动脚本 swift-init object-auditor start 启动脚本会运行swift源码bin目录下的swift-ojbect-auditor if __name__ == '__main__': parser = OptionParser("%prog CONFIG [options]") parser.add_option('-

nginx源码分析--从源码看nginx框架总结

nginx源码总结: 1)代码中没有特别绕特别别扭的编码实现,从变量的定义调用函数的实现封装,都非常恰当,比如从函数命名或者变量命名就可以看出来定义的大体意义,函数的基本功能,再好的架构实现在编码习惯差的人实现也会黯然失色,如果透彻理解代码的实现,领悟架构的设计初衷,觉得每块代码就想经过耐心雕琢一样,不仅仅实现了基本的功能给你,为其他人阅读也会提供很好的支持.细致恰当的命名规则就可以看出作者的功力. 2)更好更高的软件性能体现在架构设计上,好的架构会让软件更加稳定.容易维护.便于扩展.从核心模块

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

1. 分析kafka源码的目的 深入掌握kafka的内部原理 深入掌握scala运用 2. server的启动 如下所示(本来准备用时序图的,但感觉时序图没有思维图更能反映,故采用了思维图): 2.1 启动入口Kafka.scala 从上面的思维导图,可以看到Kafka的启动入口是Kafka.scala的main()函数: def main(args: Array[String]): Unit = { try { val serverProps = getPropsFromArgs(args)

老李推荐:第6章5节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-事件

老李推荐:第6章5节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-事件 从网络过来的命令字串需要解析翻译出来,有些命令会在翻译好后直接执行然后返回,但有一大部分命令在翻译后需要转换成对应的事件,然后放入到命令队列里面等待执行.Monkey在取出一个事件执行的时候主要是执行其injectEvent方法来注入事件,而注入事件根据是否需要往系统注入事件分为两种: 需要通过系统服务往系统注入事件:如MonkeyKeyEvent事件会通过系统的InputManager往系

老李推荐:第6章3节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-命令翻译类

老李推荐:第6章3节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-命令翻译类 每个来自网络的字串命令都需要进行解析执行,只是有些是在解析的过程中直接执行了事,而有些是需要在解析后创建相应的事件类实例并添加到命令队列里面排队执行.负责这部分工作的就是命令翻译类.那么我们往下还是继续在MonkeySourceNetwork这个范畴中MonkeyCommand类是怎么一回事: 图6-3-1 MonkeyCommand族谱 图中间的MonkeyCommand是一个接口,