在开发中使用mybatis,是将Mapper接口生成的代理类存入到springIOC容器中。然后,通过注解获取代理对象,调用对应接口方法,间接调用到mapperxml中的SQL。 如果想了解mybatis是如何通过接口实现SQL操作可以看一下这个:https:www。toutiao。comarticle7206541912168268343 一般接口都会有入参,和返回对象。那么mybatis是如何进行封装的呢? mapperxml文件中的语句经常会使用一些标签if、foreach、set、where等,是如何实现的呢? Mybatis参数的解析 参数解析这一步骤,通常是在执行SQL语句之前就要完成的,因此,需要查看SQL执行之前的代码。问题是从哪里开始查看源码呢?既然不知道具体是哪里进行了参数解析,那就从调用代理对象开始。 代理对象调用方法执行MapperProxy。invoke方法,MapperProxy。invoke方法中调用MapperProxy。cachedInvoker方法,并且该方法中创建一个MapperMethod实例对象。 MapperMethod对象中有两个成员常量,SqlCommand和MethodSignature对象。后面会用到。 最终MapperProxy。cachedInvoker方法返回一个PlainMethodInvoker对象,接着PlainMethodInvoker对象调用自己的invoke方法(注意:这就是一个invoke方法,跟代理没关系,不要被迷惑了) 调用方法后,执行MapperMethod对象的execute方法。该方法中 method。convertArgsToSqlCommandParam(args),就是我们要找的参数解析方法。 这块代码可以不看。publicObjectexecute(SqlSessionsqlSession,Object〔〕args){Objectresult;switch(command。getType()){caseINSERT:{Objectparammethod。convertArgsToSqlCommandParam(args);resultrowCountResult(sqlSession。insert(command。getName(),param));break;}caseUPDATE:{Objectparammethod。convertArgsToSqlCommandParam(args);resultrowCountResult(sqlSession。update(command。getName(),param));break;}caseDELETE:{Objectparammethod。convertArgsToSqlCommandParam(args);resultrowCountResult(sqlSession。delete(command。getName(),param));break;}caseSELECT:if(method。returnsVoid()method。hasResultHandler()){executeWithResultHandler(sqlSession,args);resultnull;}elseif(method。returnsMany()){resultexecuteForMany(sqlSession,args);}elseif(method。returnsMap()){resultexecuteForMap(sqlSession,args);}elseif(method。returnsCursor()){resultexecuteForCursor(sqlSession,args);}else{Objectparammethod。convertArgsToSqlCommandParam(args);resultsqlSession。selectOne(command。getName(),param);if(method。returnsOptional()(resultnull!method。getReturnType()。equals(result。getClass()))){resultOptional。ofNullable(result);}}break;caseFLUSH:resultsqlSession。flushStatements();break;default:thrownewBindingException(Unknownexecutionmethodfor:command。getName());}if(resultnullmethod。getReturnType()。isPrimitive()!method。returnsVoid()){thrownewBindingException(Mappermethodcommand。getName()attemptedtoreturnnullfromamethodwithaprimitivereturntype(method。getReturnType())。);}returnresult;} convertArgsToSqlCommandParam方法,MethodSignature对象是创建MapperMethod时一个成员常量。 ParamNameResolver是MethodSignature中的一个成员常量。 ParamNameResolver对象中ParamNameResolver方法。该方法获取带有param注解的参数。 ParamNameResolver是用来解析param类型的publicParamNameResolver(Configurationconfig,Methodmethod){默认使用实际参数名this。useActualParamNameconfig。isUseActualParamName();finalClasslt;?〔〕paramTypesmethod。getParameterTypes();finalAnnotation〔〕〔〕paramAnnotationsmethod。getParameterAnnotations();finalSortedMapInteger,StringmapnewTreeMap();intparamCountparamAnnotations。length;getnamesfromParamannotations获取Param注解的参数for(intparamIndex0;paramIndexparamCount;paramIndex){if(isSpecialParameter(paramTypes〔paramIndex〕)){skipspecialparameterscontinue;}Stringnamenull;for(Annotationannotation:paramAnnotations〔paramIndex〕){if(annotationinstanceofParam){hasParamAnnotationtrue;name((Param)annotation)。value();break;}}if(namenull){Paramwasnotspecified。if(useActualParamName){namegetActualParamName(method,paramIndex);}if(namenull){usetheparameterindexasthename(0,1,。。。)gcodeissue71nameString。valueOf(map。size());}}map。put(paramIndex,name);}namesCollections。unmodifiableSortedMap(map);} 用来解析实体参数(param1,param2,。。。)publicObjectgetNamedParams(Object〔〕args){finalintparamCountnames。size();if(argsnullparamCount0){returnnull;}如果只有一个参数,并且没有使用Param注解,就直接返回第一个参数elseif(!hasParamAnnotationparamCount1){Objectvalueargs〔names。firstKey()〕;returnwrapToMapIfCollection(value,useActualParamName?names。get(0):null);}有多个参数,则封装成一个map,key为参数参数名称,参数使用Param注解,名称就是注解中的值,否则key为arg0、arg1这种类型,同时,一定含有key为param1、param2的参数,值就是传入的值else{finalMapString,ObjectparamnewParamMap();inti0;for(Map。EntryInteger,Stringentry:names。entrySet()){没有Param注解,key为arg0、arg1这种类型param。put(entry。getValue(),args〔entry。getKey()〕);addgenericparamnames(param1,param2,。。。)finalStringgenericParamNameGENERICNAMEPREFIX(i1);ensurenottooverwriteparameternamedwithParamif(!names。containsValue(genericParamName)){param。put(genericParamName,args〔entry。getKey()〕);}i;}returnparam;}} 小结: ParamNameResolver对象解析参数,两种方法: ParamNameResolver方法:解析形参,判断是否使用了Param注解。 getNamedParams方法:封装实参,如果只有一个,并且没有使用Param注解,就直接返回第一个参数值,否则封装成map。多个参数,key为参数参数名称,使用Param注解,key值就是注解中的值,否则key为arg0、arg1这种类型,并且,一定含有key为param1、param2的参数,作为值传入。 上面的这一部分主要是将参数解析。 Mybatis解析好的参与SQL整合 参数解析完成后,执行SqlSession中的方法。 以selectList方法为例,首先获取MappedStatement对象,给对象包含SQL。 wrapCollection(parameter)方法对解析后的参数进行了再次封装。 对集合、数组、列表进行了二次封装,其他类型原样返回。 执行executor。query(ms,wrapCollection(parameter),rowBounds,handler)代码后,进入BaseExecutor类中query方法。 BoundSqlboundSqlms。getBoundSql(parameter); 通过mappedStatement对象,获取BoundSql对象。 然后,根据mappedStatement成员变量sqlSource调用getBoundSql。BoundSqlboundSqlsqlSource。getBoundSql(parameterObject); BoundSql语句的解析主要是通过对{}字符的解析,将其替换成?。{}中的key属性以及相应的参数映射,比如javaType、jdbcType等信息均保存至BoundSql的parameterMappings属性中。BoundSql类中的成员: ParameterObject:传入的参数。 一个参数对象时:为该参数的类型; 多个参数对象时:为ParamMap对象。 ParameterMapping:{name}形式的引用变量,变量会在解析Mapper。xml文件中的语句时,就被替换成占位符?。ParameterMapping类记录对应的变量信息。 additionalParameters:使用Criteria对象的sql。 MetaObject:Mybatis在sql参数设置和结果集映射里经常使用到这个对象。 DynamicSqlSource或者RowSqlSource,前者表示动态SQL,后者表示静态SQL。 动态SQL:行该sql相关操作的时候才根据传入的参数进行解析。(mybatis标签if、where等) 以StaticSqlSource为例: getBoundSql直接返回BoundSql对象。 代码返回到:BaseExecutor类query方法中。 query方法调用queryFromDatabase方法,queryFromDatabase再调用 doQuery(ms,parameter,rowBounds,resultHandler,boundSql)方法。 doQuery方法中prepareStatement就是给预编译SQL进行赋值的。OverridepublicEListEdoQuery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{Statementstmtnull;try{Configurationconfigurationms。getConfiguration();StatementHandlerhandlerconfiguration。newStatementHandler(wrapper,ms,parameter,rowBounds,resultHandler,boundSql);预编译sql,并且给参数赋值stmtprepareStatement(handler,ms。getStatementLog());returnhandler。query(stmt,resultHandler);}finally{closeStatement(stmt);}} prepareStatement方法中handler。parameterize(stmt)就是设置参数值。 调用PreparedStatementHandler类的parameterize方法。 parameterize方法中的parameterHandler。setParameters((PreparedStatement)statement)调用DefaultParameterHandlersetParameters方法。 typeHandler。setParameter(ps,i1,value,jdbcType); 千辛万苦终于是把解析的参数与SQL语句进行融合了。 本篇篇幅有点长,后期拆分一下。