0

0

select源码分析及小结

巴扎黑

巴扎黑

发布时间:2017-06-26 09:32:12

|

1865人浏览过

|

来源于php中文网

原创

示例代码

之前的文章说过,对于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 selectOne(String statement, Object parameter) { 2     // Popular vote was to return null on 0 results and throw exception on too many. 3     List list = this.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  List 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  List 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  List 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 list = (List) tcm.getObject(cache, key);10         if (list == null) {11           list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);12           tcm.putObject(cache, key, list); // issue #578 and #11613         }14         return list;15       }16     }17     return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);18 }

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

 1 public  List 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 list;10     try {11       queryStack++;12       list = resultHandler == null ? (List) 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 #60126       deferredLoads.clear();27       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {28         // issue #48229         clearLocalCache();30       }31     }32     return list;33 }

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

 1 private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2     List 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  List 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.query(stmt, resultHandler); 8     } finally { 9       closeStatement(stmt);10     }11 }

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

 

PreparedStatementHandler的query方法实现

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

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

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

 1 public List handleResultSets(Statement stmt) throws SQLException { 2     ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); 3  4     final List multipleResults = new ArrayList(); 5  6     int resultSetCount = 0; 7     ResultSetWrapper rsw = getFirstResultSet(stmt); 8  9     List 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行代码,获取该标签按道理应该只能定义一个resultMap属性,但是这里却获取的是一个List,不是很清楚。

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

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

第20行~第33行的代码,是用于处理

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

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

 

小结

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

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

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

ShoopD 网上商店系统
ShoopD 网上商店系统

用 php + mysql 驱动的在线商城系统,我们的目标为中国的中小企业及个人提供最简洁,最安全,最高效的在线商城解决方案,使用了自建的会员积分折扣功能,不同的会员组有不同的折扣,让您的商店吸引更多的后续客户。 系统自动加分处理功能,自动处理会员等级,免去人工处理的工作量,让您的商店运作起来更方便省事 采用了自建的直接模板技术,免去了模板解析时间,提高了代码利用效率 独立开发的购物车系统,使用最

下载

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

接着是MappedStatement,一个MappedStatement对应mapper文件中的一个

接着是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   @Override13   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 doFlushStatements(boolean isRollback) 5       throws SQLException; 6  7 protected abstract  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) 8       throws SQLException; 9 10 protected abstract  Cursor 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 interceptors = new ArrayList(); 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 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的时候会根据用户配置先尝试操作缓存。

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

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 fail10           // throw an SQLException instead of a Runtime11           checkConnection();12         }13         return method.invoke(realConnection, args);14       } catch (Throwable t) {15         throw ExceptionUtil.unwrapThrowable(t);16       }17     }18 }

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

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang处理数据库错误教程合集
Golang处理数据库错误教程合集

本专题整合了Golang数据库错误处理方法、技巧、管理策略相关内容,阅读专题下面的文章了解更多详细内容。

67

2026.02.06

java多线程方法汇总
java多线程方法汇总

本专题整合了java多线程面试题、实现函数、执行并发相关内容,阅读专题下面的文章了解更多详细内容。

32

2026.02.06

1688阿里巴巴货源平台入口与批发采购指南
1688阿里巴巴货源平台入口与批发采购指南

本专题整理了1688阿里巴巴批发进货平台的最新入口地址与在线采购指南,帮助用户快速找到官方网站入口,了解如何进行批发采购、货源选择以及厂家直销等功能,提升采购效率与平台使用体验。

489

2026.02.06

快手网页版入口与电脑端使用指南 快手官方短视频观看入口
快手网页版入口与电脑端使用指南 快手官方短视频观看入口

本专题汇总了快手网页版的最新入口地址和电脑版使用方法,详细提供快手官网直接访问链接、网页端操作教程,以及如何无需下载安装直接观看短视频的方式,帮助用户轻松浏览和观看快手短视频内容。

265

2026.02.06

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

18

2026.02.06

Python 微服务架构与 FastAPI 框架
Python 微服务架构与 FastAPI 框架

本专题系统讲解 Python 微服务架构设计与 FastAPI 框架应用,涵盖 FastAPI 的快速开发、路由与依赖注入、数据模型验证、API 文档自动生成、OAuth2 与 JWT 身份验证、异步支持、部署与扩展等。通过实际案例,帮助学习者掌握 使用 FastAPI 构建高效、可扩展的微服务应用,提高服务响应速度与系统可维护性。

29

2026.02.06

JavaScript 异步编程与事件驱动架构
JavaScript 异步编程与事件驱动架构

本专题深入讲解 JavaScript 异步编程与事件驱动架构,涵盖 Promise、async/await、事件循环机制、回调函数、任务队列与微任务队列、以及如何设计高效的异步应用架构。通过多个实际示例,帮助开发者掌握 如何处理复杂异步操作,并利用事件驱动设计模式构建高效、响应式应用。

14

2026.02.06

java连接字符串方法汇总
java连接字符串方法汇总

本专题整合了java连接字符串教程合集,阅读专题下面的文章了解更多详细操作。

69

2026.02.05

java中fail含义
java中fail含义

本专题整合了java中fail的含义、作用相关内容,阅读专题下面的文章了解更多详细内容。

32

2026.02.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 8.7万人学习

Git 教程
Git 教程

共21课时 | 3.5万人学习

MySQL 教程
MySQL 教程

共48课时 | 2.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号