您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

select源码分析及小结

2025/3/13 11:27:09发布30次查看
示例代码
之前的文章说过,对于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 #11613         }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 #60126       deferredloads.clear();27       if (configuration.getlocalcachescope() == localcachescope.statement) {28         // issue #48229         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   @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<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的时候会根据用户配置先尝试操作缓存。
在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中的插件也是使用代理模式实现的,这个在后面会说到。
以上就是select源码分析及小结的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product