MyBatis执行Sql的流程实例解析
这篇文章主要介绍了MyBatis执行Sql的流程实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
本博客着重介绍MyBatis执行Sql的流程,关于在执行过程中缓存、动态SQl生成等细节不在本博客中体现,相应内容后面再单独写博客分析吧。
还是以之前的查询作为列子:
publicclassUserDaoTest{ privateSqlSessionFactorysqlSessionFactory; @Before publicvoidsetUp()throwsException{ ClassPathResourceresource=newClassPathResource("mybatis-config.xml"); InputStreaminputStream=resource.getInputStream(); sqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream); } @Test publicvoidselectUserTest(){ Stringid="{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}"; SqlSessionsqlSession=sqlSessionFactory.openSession(); CbondissuerMappercbondissuerMapper=sqlSession.getMapper(CbondissuerMapper.class); Cbondissuercbondissuer=cbondissuerMapper.selectByPrimaryKey(id); System.out.println(cbondissuer); sqlSession.close(); } }
之前提到拿到sqlSession之后就能进行各种CRUD操作了,所以我们就从sqlSession.getMapper这个方法开始分析,看下整个Sql的执行流程是怎么样的。
获取Mapper
进入sqlSession.getMapper方法,会发现调的是Configration对象的getMapper方法:
publicTgetMapper(Class type,SqlSessionsqlSession){ //mapperRegistry实质上是一个Map,里面注册了启动过程中解析的各种Mapper.xml //mapperRegistry的key是接口的全限定名,比如com.csx.demo.spring.boot.dao.SysUserMapper //mapperRegistry的Value是MapperProxyFactory,用于生成对应的MapperProxy(动态代理类) returnmapperRegistry.getMapper(type,sqlSession); }
进入getMapper方法:
publicTgetMapper(Class type,SqlSessionsqlSession){ finalMapperProxyFactory mapperProxyFactory=(MapperProxyFactory )knownMappers.get(type); //如果配置文件中没有配置相关Mapper,直接抛异常 if(mapperProxyFactory==null){ thrownewBindingException("Type"+type+"isnotknowntotheMapperRegistry."); } try{ //关键方法 returnmapperProxyFactory.newInstance(sqlSession); }catch(Exceptione){ thrownewBindingException("Errorgettingmapperinstance.Cause:"+e,e); } }
进入MapperProxyFactory的newInstance方法:
publicclassMapperProxyFactory{ privatefinalClass mapperInterface; privatefinalMap methodCache=newConcurrentHashMap (); publicMapperProxyFactory(Class mapperInterface){ this.mapperInterface=mapperInterface; } publicClass getMapperInterface(){ returnmapperInterface; } publicMap getMethodCache(){ returnmethodCache; } //生成Mapper接口的动态代理类MapperProxy @SuppressWarnings("unchecked") protectedTnewInstance(MapperProxy mapperProxy){ return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),newClass[]{mapperInterface},mapperProxy); } publicTnewInstance(SqlSessionsqlSession){ finalMapperProxy mapperProxy=newMapperProxy (sqlSession,mapperInterface,methodCache); returnnewInstance(mapperProxy); } }
下面是动态代理类MapperProxy,调用Mapper接口的所有方法都会先调用到这个代理类的invoke方法(注意由于Mybatis中的Mapper接口没有实现类,所以MapperProxy这个代理对象中没有委托类,也就是说MapperProxy干了代理类和委托类的事情)。好了下面重点看下invoke方法。
//MapperProxy代理类 publicclassMapperProxyimplementsInvocationHandler,Serializable{ privatestaticfinallongserialVersionUID=-6424540398559729838L; privatefinalSqlSessionsqlSession; privatefinalClass mapperInterface; privatefinalMap methodCache; publicMapperProxy(SqlSessionsqlSession,Class mapperInterface,Map methodCache){ this.sqlSession=sqlSession; this.mapperInterface=mapperInterface; this.methodCache=methodCache; } @Override publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{ try{ if(Object.class.equals(method.getDeclaringClass())){ returnmethod.invoke(this,args); }elseif(isDefaultMethod(method)){ returninvokeDefaultMethod(proxy,method,args); } }catch(Throwablet){ throwExceptionUtil.unwrapThrowable(t); } //获取MapperMethod,并调用MapperMethod finalMapperMethodmapperMethod=cachedMapperMethod(method); returnmapperMethod.execute(sqlSession,args); } privateMapperMethodcachedMapperMethod(Methodmethod){ MapperMethodmapperMethod=methodCache.get(method); if(mapperMethod==null){ mapperMethod=newMapperMethod(mapperInterface,method,sqlSession.getConfiguration()); methodCache.put(method,mapperMethod); } returnmapperMethod; } @UsesJava7 privateObjectinvokeDefaultMethod(Objectproxy,Methodmethod,Object[]args) throwsThrowable{ finalConstructor constructor=MethodHandles.Lookup.class .getDeclaredConstructor(Class.class,int.class); if(!constructor.isAccessible()){ constructor.setAccessible(true); } finalClass>declaringClass=method.getDeclaringClass(); returnconstructor .newInstance(declaringClass, MethodHandles.Lookup.PRIVATE|MethodHandles.Lookup.PROTECTED |MethodHandles.Lookup.PACKAGE|MethodHandles.Lookup.PUBLIC) .unreflectSpecial(method,declaringClass).bindTo(proxy).invokeWithArguments(args); } /** *Backportofjava.lang.reflect.Method#isDefault() */ privatebooleanisDefaultMethod(Methodmethod){ return((method.getModifiers() &(Modifier.ABSTRACT|Modifier.PUBLIC|Modifier.STATIC))==Modifier.PUBLIC) &&method.getDeclaringClass().isInterface(); } }
所以这边需要进入MapperMethod的execute方法:
publicObjectexecute(SqlSessionsqlSession,Object[]args){ Objectresult; //判断是CRUD那种方法 switch(command.getType()){ caseINSERT:{ Objectparam=method.convertArgsToSqlCommandParam(args); result=rowCountResult(sqlSession.insert(command.getName(),param)); break; } caseUPDATE:{ Objectparam=method.convertArgsToSqlCommandParam(args); result=rowCountResult(sqlSession.update(command.getName(),param)); break; } caseDELETE:{ Objectparam=method.convertArgsToSqlCommandParam(args); result=rowCountResult(sqlSession.delete(command.getName(),param)); break; } caseSELECT: if(method.returnsVoid()&&method.hasResultHandler()){ executeWithResultHandler(sqlSession,args); result=null; }elseif(method.returnsMany()){ result=executeForMany(sqlSession,args); }elseif(method.returnsMap()){ result=executeForMap(sqlSession,args); }elseif(method.returnsCursor()){ result=executeForCursor(sqlSession,args); }else{ Objectparam=method.convertArgsToSqlCommandParam(args); result=sqlSession.selectOne(command.getName(),param); } break; caseFLUSH: result=sqlSession.flushStatements(); break; default: thrownewBindingException("Unknownexecutionmethodfor:"+command.getName()); } if(result==null&&method.getReturnType().isPrimitive()&&!method.returnsVoid()){ thrownewBindingException("Mappermethod'"+command.getName() +"attemptedtoreturnnullfromamethodwithaprimitivereturntype("+method.getReturnType()+")."); } returnresult; }
然后,通过一层一层的调用,最终会来到doQuery方法,这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:
publicList doQuery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{ Statementstmt=null; try{ Configurationconfiguration=ms.getConfiguration(); //内部封装了ParameterHandler和ResultSetHandler StatementHandlerhandler=configuration.newStatementHandler(wrapper,ms,parameter,rowBounds,resultHandler,boundSql); stmt=prepareStatement(handler,ms.getStatementLog()); //StatementHandler封装了Statement,让StatementHandler去处理 returnhandler. query(stmt,resultHandler); }finally{ closeStatement(stmt); } }
接下来,咱们看看StatementHandler的一个实现类PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement),看看它使怎么去处理的:
publicList query(Statementstatement,ResultHandlerresultHandler)throwsSQLException{ //到此,原形毕露,PreparedStatement,这个大家都已经滚瓜烂熟了吧 PreparedStatementps=(PreparedStatement)statement; ps.execute(); //结果交给了ResultSetHandler去处理,处理完之后返回给客户端 returnresultSetHandler. handleResultSets(ps); }
到此,整个调用流程结束。
简单总结
这边结合获取SqlSession的流程,做下简单的总结:
- SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;
- 拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor组件中包含了Transaction对象),这个Sql执行器会代理你配置的拦截器方法。
- 获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象和上面创建的Executor对象,所以通过SqlSession也能拿到全局配置;
- 获得SqlSession对象后就能执行各种CRUD方法了。
以上是获得SqlSession的流程,下面总结下本博客中介绍的Sql的执行流程:
- 调用SqlSession的getMapper方法,获得Mapper接口的动态代理对象MapperProxy,调用Mapper接口的所有方法都会调用到MapperProxy的invoke方法(动态代理机制);
- MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象,然后调用这个对象的execute方法,sqlSession会作为execute方法的入参;
- 往下,层层调下来会进入Executor组件(如果配置插件会对Executor进行动态代理)的query方法,这个方法中会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数。
Executor组件有两个直接实现类,分别是BaseExecutor和CachingExecutor。CachingExecutor静态代理了BaseExecutor。Executor组件封装了Transction组件,Transction组件中又分装了Datasource组件。
- 调用StatementHandler的增删改查方法获得结果,ResultSetHandler对结果进行封装转换,请求结束。
Executor、StatementHandler、ParameterHandler、ResultSetHandler,Mybatis的插件会对上面的四个组件进行动态代理。
重要类
- MapperProxyFactory
- MapperProxy
- MapperMethod
- SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
- Executor:MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护;
- StatementHandler封装了JDBCStatement操作,负责对JDBCstatement的操作,如设置参数、将Statement结果集转换成List集合。
- ParameterHandler负责对用户传递的参数转换成JDBCStatement所需要的参数,
- ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
- TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换
- MappedStatementMappedStatement维护了一条
- SqlSource负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql表示动态生成的SQL语句以及相应的参数信息
ConfigurationMyBatis所有的配置信息都维持在Configuration对象之中。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。