300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > mybatis工作原理_万字好文!MyBatis 的工作原理 你了解过吗?

mybatis工作原理_万字好文!MyBatis 的工作原理 你了解过吗?

时间:2024-01-17 09:28:53

相关推荐

mybatis工作原理_万字好文!MyBatis 的工作原理 你了解过吗?

回复1024有特别礼包

作者:江南入直 | 来源:/scuury/p/10371246.html

上一篇:微信支付的架构到底有多牛?

近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了。

核心部件:

SqlSessionExecutorStatementHandlerParameterHandlerResultSetHandlerTypeHandlerMappedStatementConfiguration

在分析工作原理之前,首先看一下我的mybatis全局配置文件

<?xml version="1.0"encoding="UTF-8"?>

configuration

PUBLIC"-////DTDConfig3.0//EN"

"/dtd/mybatis-3-config.dtd">

default="development">"development">"JDBC"/>"POOLED">"driver"value="com.mysql.jdbc.Driver"/>"url"

value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"/>"username"value="root"/>"password"value="123456"/>"sqlMapper/userMapper.xml"/>

第一步:创建一个sqlSessionFactory

在了解如何创建sqlSessionFactory之前,先看一下mybatis是如何加载全局配置文件,解析xml文件生成Configuration的

publicConfigurationparse(){

if(parsed){

thrownewBuilderException("EachXMLConfigBuildercanonlybeusedonce.");

}

parsed=true;

parseConfiguration(parser.evalNode("/configuration"));

returnconfiguration;

}

privatevoidparseConfiguration(XNoderoot){

try{

propertiesElement(root.evalNode("properties"));//issue#117readpropertiesfirst

typeAliasesElement(root.evalNode("typeAliases"));

pluginElement(root.evalNode("plugins"));

objectFactoryElement(root.evalNode("objectFactory"));

objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

settingsElement(root.evalNode("settings"));

environmentsElement(root.evalNode("environments"));//readitafterobjectFactoryandobjectWrapperFactoryissue#631

databaseIdProviderElement(root.evalNode("databaseIdProvider"));

typeHandlerElement(root.evalNode("typeHandlers"));

mapperElement(root.evalNode("mappers"));

}catch(Exceptione){

thrownewBuilderException("ErrorparsingSQLMapperConfiguration.Cause:"+e,e);

}

}

在上面的第二段代码中有一句

mapperElement(root.evalNode("mappers"));

刚好我们的全局配置文件中有一个mapper的配置,由此可见,mapperElemet()方法是解析mapper映射文件的,具体代码如下

privatevoidmapperElement(XNodeparent)throwsException{

if(parent!=null){

for(XNodechild:parent.getChildren()){

if("package".equals(child.getName())){

StringmapperPackage=child.getStringAttribute("name");

configuration.addMappers(mapperPackage);

}else{

Stringresource=child.getStringAttribute("resource");

Stringurl=child.getStringAttribute("url");

StringmapperClass=child.getStringAttribute("class");

if(resource!=null&&url==null&&mapperClass==null){//进入该判断

ErrorContext.instance().resource(resource);

InputStreaminputStream=Resources.getResourceAsStream(resource);

XMLMapperBuildermapperParser=newXMLMapperBuilder(inputStream,configuration,resource,configuration.getSqlFragments());

mapperParser.parse();

}elseif(resource==null&&url!=null&&mapperClass==null){

ErrorContext.instance().resource(url);

InputStreaminputStream=Resources.getUrlAsStream(url);

XMLMapperBuildermapperParser=newXMLMapperBuilder(inputStream,configuration,url,configuration.getSqlFragments());

mapperParser.parse();

}elseif(resource==null&&url==null&&mapperClass!=null){

Class>mapperInterface=Resources.classForName(mapperClass);

configuration.addMapper(mapperInterface);

}else{

thrownewBuilderException("Amapperelementmayonlyspecifyaurl,resourceorclass,butnotmorethanone.");

}

}

}

}

}

根据以上代码可以分析,在写mapper映射文件的地址时不仅可以写成resource,还可以写成url和mapperClass的形式,由于我们用的是resource,所以直接进入第一个判断,最后解析mapper映射文件的方法是

privatevoidconfigurationElement(XNodecontext){

try{

Stringnamespace=context.getStringAttribute("namespace");

if(namespace.equals("")){

thrownewBuilderException("Mapper'snamespacecannotbeempty");

}

builderAssistant.setCurrentNamespace(namespace);

cacheRefElement(context.evalNode("cache-ref"));

cacheElement(context.evalNode("cache"));

parameterMapElement(context.evalNodes("/mapper/parameterMap"));

resultMapElements(context.evalNodes("/mapper/resultMap"));

sqlElement(context.evalNodes("/mapper/sql"));

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

}catch(Exceptione){

thrownewBuilderException("ErrorparsingMapperXML.Cause:"+e,e);

}

}

其中具体解析每一个sql语句节点的是

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

进入这个方法一层层深究,最后到这里可以知道MappedStatement是由builderAssistant(即MapperBuildAssistant)创建的。

publicvoidparseStatementNode(){

...

builderAssistant.addMappedStatement(id,sqlSource,statementType,sqlCommandType,

fetchSize,timeout,parameterMap,parameterTypeClass,resultMap,resultTypeClass,

resultSetTypeEnum,flushCache,useCache,resultOrdered,

keyGenerator,keyProperty,keyColumn,databaseId,langDriver,resultSets);

}

最后进入方法addMappedStatement(),mappedStatement最后以id为键保存在了Configuration中的一个map变量mappedStatements中。

publicMappedStatementaddMappedStatement(

Stringid,

SqlSourcesqlSource,

StatementTypestatementType,

SqlCommandTypesqlCommandType,

IntegerfetchSize,

Integertimeout,

StringparameterMap,

Class>parameterType,

StringresultMap,

Class>resultType,

ResultSetTyperesultSetType,booleanflushCache,booleanuseCache,booleanresultOrdered,

KeyGeneratorkeyGenerator,

StringkeyProperty,

StringkeyColumn,

StringdatabaseId,

LanguageDriverlang,

StringresultSets){

if(unresolvedCacheRef)thrownewIncompleteElementException("Cache-refnotyetresolved");

id=applyCurrentNamespace(id,false);

booleanisSelect=sqlCommandType==SqlCommandType.SELECT;

MappedStatement.BuilderstatementBuilder=newMappedStatement.Builder(configuration,id,sqlSource,sqlCommandType);

statementBuilder.resource(resource);

statementBuilder.fetchSize(fetchSize);

statementBuilder.statementType(statementType);

statementBuilder.keyGenerator(keyGenerator);

statementBuilder.keyProperty(keyProperty);

statementBuilder.keyColumn(keyColumn);

statementBuilder.databaseId(databaseId);

statementBuilder.lang(lang);

statementBuilder.resultOrdered(resultOrdered);

statementBuilder.resulSets(resultSets);

setStatementTimeout(timeout,statementBuilder);

setStatementParameterMap(parameterMap,parameterType,statementBuilder);

setStatementResultMap(resultMap,resultType,resultSetType,statementBuilder);

setStatementCache(isSelect,flushCache,useCache,currentCache,statementBuilder);

MappedStatementstatement=statementBuilder.build();

configuration.addMappedStatement(statement);

returnstatement;

}

最后回到我们的创建sqlSessionFactory上,之前的一切都是为了生成一个sqlSessionFactory服务的

搜索公众号顶级架构师回复关键字“offer”,获取一份算法面试题和答案惊喜礼包。

publicSqlSessionFactorybuild(InputStreaminputStream,Stringenvironment,Propertiesproperties){

try{

XMLConfigBuilderparser=newXMLConfigBuilder(inputStream,environment,properties);

returnbuild(parser.parse());

}catch(Exceptione){

throwExceptionFactory.wrapException("ErrorbuildingSqlSession.",e);

}finally{

ErrorContext.instance().reset();

try{

inputStream.close();

}catch(IOExceptione){

//Intentionallyignore.Preferpreviouserror.

}

}

}

publicSqlSessionFactorybuild(Configurationconfig){

returnnewDefaultSqlSessionFactory(config);

}

从上面的代码可以看出最后是通过以Configuration为参数build()方法生成DefautSqlSessionFactory。

第二步:创建sqlSession

publicSqlSessionopenSession(){

returnopenSessionFromDataSource(configuration.getDefaultExecutorType(),null,false);

}

privateSqlSessionopenSessionFromDataSource(ExecutorTypeexecType,TransactionIsolationLevellevel,booleanautoCommit){

Transactiontx=null;

try{

finalEnvironmentenvironment=configuration.getEnvironment();

finalTransactionFactorytransactionFactory=getTransactionFactoryFromEnvironment(environment);

tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCommit);

finalExecutorexecutor=configuration.newExecutor(tx,execType);

returnnewDefaultSqlSession(configuration,executor,autoCommit);

}catch(Exceptione){

closeTransaction(tx);//mayhavefetchedaconnectionsoletscallclose()

throwExceptionFactory.wrapException("Erroropeningsession.Cause:"+e,e);

}finally{

ErrorContext.instance().reset();

}

}

//返回一个SqlSession,默认使用DefaultSqlSession

publicDefaultSqlSession(Configurationconfiguration,Executorexecutor,booleanautoCommit){

this.configuration=configuration;

this.executor=executor;

this.dirty=false;

this.autoCommit=autoCommit;

}

executor在这一步得到创建,具体的使用在下一步。

第三步:执行具体的sql请求

在我的代码里执行的是

Useruser=sqlSession.selectOne("test.findUserById",1);

具体到里面的方法就是

publicListselectList(Stringstatement,Objectparameter,RowBoundsrowBounds){try{//1.根据StatementId,在mybatis配置对象Configuration中查找和配置文件相对应的MappedStatement

MappedStatementms=configuration.getMappedStatement(statement);//2.将查询任务委托给MyBatis的执行器Executor

Listresult=executor.query(ms,wrapCollection(parameter),rowBounds,Executor.NO_RESULT_HANDLER);returnresult;

}catch(Exceptione){throwExceptionFactory.wrapException("Errorqueryingdatabase.Cause:"+e,e);

}finally{

ErrorContext.instance().reset();

}

}

在这里通过statementId拿到了我们在第一步存在map里面的MappedStatement。在这里引用参考博客的一句话:

SqlSession根据Statement ID, 在mybatis配置对象Configuration中获取到对应的MappedStatement对象,然后调用mybatis执行器来执行具体的操作。

再继续看query()和queryFromDatabase()这两个方法

@SuppressWarnings("unchecked")

publicListquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{

ErrorContext.instance().resource(ms.getResource()).activity("executingaquery").object(ms.getId());if(closed)thrownewExecutorException("Executorwasclosed.");if(queryStack==0&&ms.isFlushCacheRequired()){

clearLocalCache();

}

Listlist;try{

queryStack++;

list=resultHandler==null?(List)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(DeferredLoaddeferredLoad:deferredLoads){

deferredLoad.load();

}

deferredLoads.clear();//issue#601if(configuration.getLocalCacheScope()==LocalCacheScope.STATEMENT){

clearLocalCache();//issue#482

}

}returnlist;

}

privateListqueryFromDatabase(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{

Listlist;

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

}returnlist;

}

在这两个方法里面会为当前的查询创建一个缓存key,如果缓存中没有值,直接从数据库中读取,执行查询后将得到的list结果放入缓存之中。

紧接着看doQuery()在SimpleExecutor类中重写的方法

publicListdoQuery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{

Statementstmt=null;try{

Configurationconfiguration=ms.getConfiguration();

StatementHandlerhandler=configuration.newStatementHandler(wrapper,ms,parameter,rowBounds,resultHandler,boundSql);

stmt=prepareStatement(handler,ms.getStatementLog());returnhandler.query(stmt,resultHandler);

}finally{

closeStatement(stmt);

}

}

Statement连接对象就是在这里创建的,因此Executor的作用之一就是创建Statement了,创建完后又把Statement丢给StatementHandler返回List查询结果。

接下来再看一下这里的两个方法prepareStatement()和query()的具体实现

privateStatementprepareStatement(StatementHandlerhandler,LogstatementLog)throwsSQLException{

Statementstmt;

Connectionconnection=getConnection(statementLog);

stmt=handler.prepare(connection);

handler.parameterize(stmt);

returnstmt;

}

publicListquery(Statementstatement,ResultHandlerresultHandler)throwsSQLException{

PreparedStatementps=(PreparedStatement)statement;

ps.execute();returnresultSetHandler.handleResultSets(ps);

}

prepareStatement()是创建Statement的具体实现方法,调用parameterize()对创建的Statement对象设置参数,即为我们设为占位符的地方赋上指定的参数,parameterize()方法再深入进去就是调用ParameterHandler的setParameters()方法具体赋值了。

搜索公众号顶级架构师回复关键字“架构整洁”,获取一份惊喜礼包。

这里的query()是调用了ResultSetHandler的handleResultSets(Statement) 方法。作用就是把ResultSet结果集对象转换成List类型的集合。

总结以上步骤就是:

根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示为当前的查询创建一个缓存Key缓存中没有值,直接从数据库中读取数据执行查询,返回List 结果,然后 将查询的结果放入缓存之中根据既有的参数,创建StatementHandler对象来执行查询操作将创建Statement传递给StatementHandler对象,调用parameterize()方法赋值调用StatementHandler.query()方法,返回List结果集

总结

以上三个步骤所有流程大体可以用一张图来总结

PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。

大家一起在评论区聊聊呗~

公众号后台回复架构或者架构整洁有惊喜礼包!顶级架构师交流群

「顶级架构师」建立了读者架构师交流群,大家可以添加小编微信进行加群。欢迎有想法、乐于分享的架构师们一起交流学习。

扫描添加好友邀你进架构师群,加我时注明姓名+公司+职位】

版权申明:内容来源网络,版权归原作者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

猜你还想看

Java基于Swing和Netty仿QQ界面聊天小项目

图解 Docker 架构

Java压缩20M文件从30秒到1秒的优化过程

MyBatis 如何兼容所有日志框架?

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。