one方法报错 select_【MyBatis源码分析】select源码分析及小结

本文详细分析了MyBatis中`selectOne`方法的执行流程,从`SqlSession`的`selectOne`到`selectList`,再到`Executor`的查询操作。虽然`selectOne`最终调用了`selectList`,但在结果处理时会检查是否只有一个结果,多于一个则抛出异常。源码中涉及了装饰器模式,如`CachingExecutor`为`SimpleExecutor`添加缓存功能。此外,还介绍了MyBatis中使用的设计模式,如建造者、抽象工厂、模板模式、责任链和装饰器模式等。
摘要由CSDN通过智能技术生成

示例代码

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

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

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

selectMailById方法的实现为:

1 public Mail selectMailById(longid) {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 ListselectList(String statement, Object parameter, RowBounds rowBounds) {2 try{3 MappedStatement ms =configuration.getMappedStatement(statement);4 returnexecutor.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) throwsSQLException {2 BoundSql boundSql =ms.getBoundSql(parameterObject);3 CacheKey key =createCacheKey(ms, parameterObject, rowBounds, boundSql);4 returnquery(ms, parameterObject, rowBounds, resultHandler, key, boundSql);5 }

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

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

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

1 public Listquery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)2 throwsSQLException {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 #116

13 }14 returnlist;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) throwsSQLException {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 Listlist;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 #601

26 deferredLoads.clear();27 if (configuration.getLocalCacheScope() ==LocalCacheScope.STATEMENT) {28 //issue #482

29 clearLocalCache();30 }31 }32 returnlist;33 }

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

1 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throwsSQLException {2 Listlist;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 returnlist;14 }

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

1 public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throwsSQLException {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) throwsSQLException {2 PreparedStatement ps =(PreparedStatement) statement;3 ps.execute();4 return resultSetHandler.handleResultSets(ps);5 }

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

1 public List handleResultSets(Statement stmt) throwsSQLException {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

35 returncollapseSingleResultList(multipleResults);36 }

总结一下这个方法。

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

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

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

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

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

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

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

小结

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

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

7624202029ed52d367293d8650327ab9.png

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

259208502f59848e18e9dd54663e0224.png

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

9a5c83bc60568fa5136e07e16827c889.png

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

ff774eb499746dca0ffd8b57015e5500.png

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

c24737eff3cac10fd66d0eb8b832cf65.png

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

32b1da5c3f3a1be676a0042cb92a6939.png

MyBatis中使用到的设计模式

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

1、建造者模式

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

1 publicSqlSessionFactory build(Reader reader) {2 return build(reader, null, null);3 }4

5 publicSqlSessionFactory build(Reader reader, String environment) {6 return build(reader, environment, null);7 }8

9 publicSqlSessionFactory build(Reader reader, Properties properties) {10 return build(reader, null, properties);11 }12

13 publicSqlSessionFactory build(Reader reader, String environment, Properties properties) {14 try{15 XMLConfigBuilder parser = newXMLConfigBuilder(reader, environment, properties);16 returnbuild(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 implementsTransactionFactory {2

3 @Override4 public voidsetProperties(Properties props) {5 }6

7 @Override8 publicTransaction newTransaction(Connection conn) {9 return newJdbcTransaction(conn);10 }11

12 @Override13 public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, booleanautoCommit) {14 return newJdbcTransaction(ds, level, autoCommit);15 }16 }

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

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

3、模板模式

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

1 protected abstract intdoUpdate(MappedStatement ms, Object parameter)2 throwsSQLException;3

4 protected abstract List doFlushStatements(booleanisRollback)5 throwsSQLException;6

7 protected abstract ListdoQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)8 throwsSQLException;9

10 protected abstract CursordoQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)11 throws SQLException;

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

4、责任链模式

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

1 public classInterceptorChain {2

3 private final List interceptors = new ArrayList();4

5 publicObject pluginAll(Object target) {6 for(Interceptor interceptor : interceptors) {7 target =interceptor.plugin(target);8 }9 returntarget;10 }11

12 public voidaddInterceptor(Interceptor interceptor) {13 interceptors.add(interceptor);14 }15

16 public ListgetInterceptors() {17 returnCollections.unmodifiableList(interceptors);18 }19

20 }

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

5、装饰器模式

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

1 public class CachingExecutor implementsExecutor {2

3 privateExecutor delegate;4 private TransactionalCacheManager tcm = newTransactionalCacheManager();5

6 publicCachingExecutor(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) throwsThrowable {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 Runtime

11 checkConnection();12 }13 returnmethod.invoke(realConnection, args);14 } catch(Throwable t) {15 throwExceptionUtil.unwrapThrowable(t);16 }17 }18 }

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值