专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

mybatis源码注解sql

  Demo
  主启动类
  javapublicclassMybatisHelloWorld{publicstaticvoidmain(String〔〕args)throwsException{Stringresourceorgmybatisconfig。xml;InputStreaminputStreamResources。getResourceAsStream(resource);SqlSessionFactorysqlSessionFactorynewSqlSessionFactoryBuilder()。build(inputStream);SqlSessionsessionsqlSessionFactory。openSession();UserMappermappersession。getMapper(UserMapper。class);ListUserusersmapper。getUsers(1);session。close();}}
  userMapper。class
  javapublicinterfaceUserMapper{Select({selectfromuserwhereage{age}})ListUsergetUsers(intage);}
  config。xml
  xmllt;?xmlversion1。0encodingUTF8?!DOCTYPEconfigurationPUBLICmybatis。orgDTDConfig3。0ENhttp:mybatis。orgdtdmybatis3config。dtdconfigurationsettings控制台输出sqlsettingnamelogImplvalueSTDOUTLOGGINGsettingsenvironmentsdefaultdevelopmentenvironmentiddevelopmenttransactionManagertypeJDBCdataSourcetypePOOLEDpropertynamedrivervaluecom。mysql。jdbc。Driverpropertynameurlvaluejdbc:mysql:localhost:3306mybatis?serverTimezoneUTCpropertynameusernamevaluerootpropertynamepasswordvaluerootdataSourceenvironmentenvironmentsmapperspackagenameorg。mybatis。mappermappersconfiguration
  Mybatis通过session来进行数据库的操作,sqlSessionFactory封装了session的创建,而SqlSessionFactoryBuilder又封装了sqlSessionFactory的创建
  从上面代码来看总共做了两件事读取配置文件,通过SqlSessionFactoryBuilder创建sqlSessionFactory继而创建session获取mapper进行读取数据库
  先来看如何将xml配置文件封装为对象的解析配置文件
  javanewSqlSessionFactoryBuilder()。build(inputStream);
  这里使用构造者模式来创建一个sqlSessionFactory,里面使用重载
  javapublicSqlSessionFactorybuild(InputStreaminputStream){returnbuild(inputStream,null,null);}
  最终调用
  SqlSessionFactoryBuilder。java
  javapublicSqlSessionFactorybuild(InputStreaminputStream,Stringenvironment,Propertiesproperties){try{创建一个xml解析类XMLConfigBuilderparsernewXMLConfigBuilder(inputStream,environment,properties);解析xml中配置,转换为configuration类returnbuild(parser。parse());}catch(Exceptione){throwExceptionFactory。wrapException(ErrorbuildingSqlSession。,e);}finally{ErrorContext。instance()。reset();try{inputStream。close();}catch(IOExceptione){Intentionallyignore。Preferpreviouserror。}}}
  mybatis是把一些配置类以及它自己需要使用的各种类封装成一个大的config对象
  org。apache。ibatis。session。Configuration里面有很多环境,mapper等等的信息,内容太多就不粘贴了
  XMLConfigBuilder。java
  javapublicXMLConfigBuilder(InputStreaminputStream,Stringenvironment,Propertiesprops){this(newXPathParser(inputStream,true,props,newXMLMapperEntityResolver()),environment,props);}privateXMLConfigBuilder(XPathParserparser,Stringenvironment,Propertiesprops){创建了一个Configuration对象super(newConfiguration());ErrorContext。instance()。resource(SQLMapperConfiguration);this。configuration。setVariables(props);this。parsedfalse;这一行设置环境idthis。environmentenvironment;this。parserparser;}
  XMlConfigBuilder类关系图
  BaseBuilder。java
  javapublicBaseBuilder(Configurationconfiguration){this。configurationconfiguration;this。typeAliasRegistrythis。configuration。getTypeAliasRegistry();this。typeHandlerRegistrythis。configuration。getTypeHandlerRegistry();}
  解析主配置文件。xml
  SqlSessionFactoryBuilder。java
  javapublicSqlSessionFactorybuild(InputStreaminputStream,Stringenvironment,Propertiesproperties){。。。。returnbuild(parser。parse());。。。。}publicConfigurationparse(){if(parsed){thrownewBuilderException(EachXMLConfigBuildercanonlybeusedonce。);}parsedtrue;读取configuration节点下的node传入parseConfiguration(parser。evalNode(configuration));returnconfiguration;}privatevoidparseConfiguration(XNoderoot){try{issue117readpropertiesfirst读取propertiespropertiesElement(root。evalNode(properties));读取一些setting设置PropertiessettingssettingsAsProperties(root。evalNode(settings));loadCustomVfs(settings);注册别名typeAliasesElement(root。evalNode(typeAliases));插件,进行增强先略过pluginElement(root。evalNode(plugins));对象工厂,自定义实例化方法略过objectFactoryElement(root。evalNode(objectFactory));objectWrapperFactoryElement(root。evalNode(objectWrapperFactory));reflectionFactoryElement(root。evalNode(reflectionFactory));settingsElement(settings);readitafterobjectFactoryandobjectWrapperFactoryissue631配置环境environmentsElement(root。evalNode(environments));数据厂商表示略过databaseIdProviderElement(root。evalNode(databaseIdProvider));typeHandlerElement(root。evalNode(typeHandlers));配置mappermapperElement(root。evalNode(mappers));}catch(Exceptione){thrownewBuilderException(ErrorparsingSQLMapperConfiguration。Cause:e,e);}}
  解析的东西很多,我们只先看environments和mapperenvironmentsElement
  XMLConfigBuilder。java
  javaprivatevoidenvironmentsElement(XNodecontext)throwsException{if(context!null){if(environmentnull){environmentcontext。getStringAttribute(default);}for(XNodechild:context。getChildren()){Stringidchild。getStringAttribute(id);可以配置多个环境,判断是不是指定的环境if(isSpecifiedEnvironment(id)){获取事物管理器,创建事物管理器工厂TransactionFactorytxFactorytransactionManagerElement(child。evalNode(transactionManager));获取datasource工厂UnpooledDataSourceFactory默认DataSourceFactorydsFactorydataSourceElement(child。evalNode(dataSource));DataSourcedataSourcedsFactory。getDataSource();Environment。BuilderenvironmentBuildernewEnvironment。Builder(id)。transactionFactory(txFactory)。dataSource(dataSource);configuration。setEnvironment(environmentBuilder。build());}}}}
  进入发现第一件是就是判断环境,没有指定就使用中default的环境id,在上面的XMLConfigBuilder的有参构造中this。environmentenvironment;将环境配置设置给了XMLConfigBuilder的environment点我跳转到XMLConfigBuilder有参构造
  我们在使用时可以这样,在配置文件xml中,声明多个环境
  xmlenvironmentsdefaultdevelopmentenvironmentiddevelopmenttransactionManagertypeJDBCdataSourcetypePOOLEDpropertynamedrivervaluecom。mysql。jdbc。Driverpropertynameurlvaluejdbc:mysql:localhost:3306mybatis?serverTimezoneUTCpropertynameusernamevaluerootpropertynamepasswordvaluerootdataSourceenvironmentenvironmentidmyTesttransactionManagertypeJDBCdataSourcetypePOOLEDpropertynamedrivervaluecom。mysql。jdbc。Driverpropertynameurlvaluejdbc:mysql:localhost:3306mybatis?serverTimezoneUTCpropertynameusernamevaluerootpropertynamepasswordvaluerootdataSourceenvironmentenvironments
  主启动类中,手动指明一个配置环境
  javapublicstaticvoidmain(String〔〕args)throwsException{。。。。SqlSessionFactorysqlSessionFactorynewSqlSessionFactoryBuilder()。build(inputStream,myTest);。。。。}
  回到代码第一步就是判断用户选择的那个id的环境,之后创建事务管理器
  XMLConfigBuilder。java
  javaprivatevoidenvironmentsElement(XNodecontext)throwsException{。。TransactionFactorytxFactorytransactionManagerElement(child。evalNode(transactionManager));。。}privateTransactionFactorytransactionManagerElement(XNodecontext)throwsException{if(context!null){获取我们在xml中声明的事务管理类型,当前是JDBCStringtypecontext。getStringAttribute(type);获取节点下的子节点,当前案例没有子节点Propertiespropscontext。getChildrenAsProperties();这里只是创建工厂类TransactionFactoryfactory(TransactionFactory)resolveClass(type)。newInstance();factory。setProperties(props);returnfactory;}thrownewBuilderException(EnvironmentdeclarationrequiresaTransactionFactory。);}
  这里调用resolveClass()方法是父类BaseBuilder的方法
  一直点进去最后如下
  TypeAliasRegistry。java
  javapublicclassTypeAliasRegistry{privatefinalMapString,Classlt;?TYPEALIASESnewHashMapString,Classlt;?();。。。。。publicTClassTresolveAlias(Stringstring){try{if(stringnull){returnnull;}issue748Stringkeystring。toLowerCase(Locale。ENGLISH);ClassTvalue;if(TYPEALIASES。containsKey(key)){value(ClassT)TYPEALIASES。get(key);}else{value(ClassT)Resources。classForName(string);}returnvalue;}catch(ClassNotFoundExceptione){thrownewTypeException(Couldnotresolvetypealiasstring。Cause:e,e);}}}
  判断这个TYPEALIASESmap中是否存在JDBC这个key,如果不存在,则去加载
  按理来说这里应该是不存在的,因为你在TypeAliasRegistry中找不到任何一个地方对TYPEALIASES添加一个JDBC的key
  但是实际它却存在这个key,在Configuration类的无参构造时,对这个TypeAliasRegistry进行的添加
  Configuration。java
  javapublicConfiguration(){typeAliasRegistry。registerAlias(JDBC,JdbcTransactionFactory。class);。。。typeAliasRegistry。registerAlias(POOLED,PooledDataSourceFactory。class);languageRegistry。setDefaultDriverClass(XMLLanguageDriver。class);。。。。。。}
  这个过程如下图
  回到代码因为我们这次案例的配置为所以不会存在子节点context。getChildrenAsProperties();返回的结果0个配置项,transactionManagerElement方法结束
  之后去解析数据库配置文件
  XMLConfigBulider。java
  javaprivatevoidenvironmentsElement(XNodecontext)throwsException{。。。获取datasource工厂UnpooledDataSourceFactory默认DataSourceFactorydsFactorydataSourceElement(child。evalNode(dataSource));DataSourcedataSourcedsFactory。getDataSource();。。。}
  和解析环境基本一样的代码,不过解析dataSource的时候,子节点就不为空了
  会有四个属性
  xmldataSourcetypePOOLEDpropertynamedrivervaluecom。mysql。jdbc。Driverpropertynameurlvaluejdbc:mysql:localhost:3306mybatis?serverTimezoneUTCpropertynameusernamevalueroot222propertynamepasswordvalueroot222dataSource
  javaprivateDataSourceFactorydataSourceElement(XNodecontext)throwsException{if(context!null){Stringtypecontext。getStringAttribute(type);Propertiespropscontext。getChildrenAsProperties();type为POOLED的默认实现是PooledDataSourceFactoryDataSourceFactoryfactory(DataSourceFactory)resolveClass(type)。newInstance();factory。setProperties(props);returnfactory;}thrownewBuilderException(EnvironmentdeclarationrequiresaDataSourceFactory。);}
  进入factory。setProperties(props);
  javapublicclassUnpooledDataSourceFactoryimplementsDataSourceFactory{privatestaticfinalStringDRIVERPROPERTYPREFIXdriver。;privatestaticfinalintDRIVERPROPERTYPREFIXLENGTHDRIVERPROPERTYPREFIX。length();protectedDataSourcedataSource;无参默认将dataSource设置为UnpooledDataSourcepublicUnpooledDataSourceFactory(){this。dataSourcenewUnpooledDataSource();}OverridepublicvoidsetProperties(Propertiesproperties){PropertiesdriverPropertiesnewProperties();将工厂对象进行包装MetaObjectmetaDataSourceSystemMetaObject。forObject(dataSource);for(Objectkey:properties。keySet()){StringpropertyName(String)key;如果存在driverif(propertyName。startsWith(DRIVERPROPERTYPREFIX)){Stringvalueproperties。getProperty(propertyName);driverProperties。setProperty(propertyName。substring(DRIVERPROPERTYPREFIXLENGTH),value);如果当前属性在类中有对应的可以写入的属性}elseif(metaDataSource。hasSetter(propertyName)){Stringvalue(String)properties。get(propertyName);ObjectconvertedValueconvertValue(metaDataSource,propertyName,value);metaDataSource。setValue(propertyName,convertedValue);}else{thrownewDataSourceException(UnknownDataSourceproperty:propertyName);}}如果属性不为空,则设置给meatDataSourceif(driverProperties。size()0){metaDataSource。setValue(driverProperties,driverProperties);}}。。。。。。}
  一顿设置后回到XMLConfigurationBuilder中的environmentsElement方法
  最后将读取出的配置封装为Environment,赋值给BaseBuilder中的environment
  javaprivatevoidenvironmentsElement(XNodecontext)throwsException{。。。。。DataSourceFactorydsFactorydataSourceElement(child。evalNode(dataSource));DataSourcedataSourcedsFactory。getDataSource();Environment。BuilderenvironmentBuildernewEnvironment。Builder(id)。transactionFactory(txFactory)。dataSource(dataSource);configuration。setEnvironment(environmentBuilder。build());。。。。。}mapperElement
  回到XMLConfigBuilder中的parseConfiguration
  javaprivatevoidparseConfiguration(XNoderoot){。。。。。配置mappermapperElement(root。evalNode(mappers));}
  我们只看根据包扫描的,给Configuration中添加了mapper包名
  javaprivatevoidmapperElement(XNodeparent)throwsException{if(parent!null){for(XNodechild:parent。getChildren()){使用包,默认查找指定包下位置if(package。equals(child。getName())){StringmapperPackagechild。getStringAttribute(name);configuration。addMappers(mapperPackage);}。。。。。}}}
  Configuration。java
  javapublicvoidaddMappers(StringpackageName){mapperRegistry。addMappers(packageName);}
  MapperRegistry。java
  javapublicvoidaddMappers(StringpackageName){addMappers(packageName,Object。class);}根据包名去查询该包下的类publicvoidaddMappers(StringpackageName,Classlt;?superType){ResolverUtilClasslt;?resolverUtilnewResolverUtilClasslt;?();resolverUtil。find(newResolverUtil。IsA(superType),packageName);SetClasslt;?extendsClasslt;?mapperSetresolverUtil。getClasses();for(Classlt;?mapperClass:mapperSet){addMapper(mapperClass);}}
  之后就是动态代理对应的mapper
  MapperRegistry。java
  javapublicTvoidaddMapper(ClassTtype){if(type。isInterface()){判断是否已经存在if(hasMapper(type)){thrownewBindingException(TypetypeisalreadyknowntotheMapperRegistry。);}booleanloadCompletedfalse;try{knownMappers。put(type,newMapperProxyFactoryT(type));MapperAnnotationBuilderparsernewMapperAnnotationBuilder(config,type);parser。parse();loadCompletedtrue;}finally{if(!loadCompleted){knownMappers。remove(type);}}}}
  javapublicclassMapperProxyFactoryT{privatefinalClassTmapperInterface;privatefinalMapMethod,MapperMethodmethodCachenewConcurrentHashMapMethod,MapperMethod();publicMapperProxyFactory(ClassTmapperInterface){this。mapperInterfacemapperInterface;}publicClassTgetMapperInterface(){returnmapperInterface;}publicMapMethod,MapperMethodgetMethodCache(){returnmethodCache;}SuppressWarnings(unchecked)protectedTnewInstance(MapperProxyTmapperProxy){return(T)Proxy。newProxyInstance(mapperInterface。getClassLoader(),newClass〔〕{mapperInterface},mapperProxy);}publicTnewInstance(SqlSessionsqlSession){finalMapperProxyTmapperProxynewMapperProxyT(sqlSession,mapperInterface,methodCache);returnnewInstance(mapperProxy);}}
  主要来看这段
  javaMapperAnnotationBuilderparsernewMapperAnnotationBuilder(config,type);parser。parse();
  MapperAnnotationBuilder。java
  javapublicclassMapperAnnotationBuilder{privatefinalSetClasslt;?extendsAnnotationsqlAnnotationTypesnewHashSetClasslt;?extendsAnnotation();privatefinalSetClasslt;?extendsAnnotationsqlProviderAnnotationTypesnewHashSetClasslt;?extendsAnnotation();。。。。在构造时添加mybatis的注解publicMapperAnnotationBuilder(Configurationconfiguration,Classlt;?type){Stringresourcetype。getName()。replace(。,)。java(bestguess);this。assistantnewMapperBuilderAssistant(configuration,resource);this。configurationconfiguration;this。typetype;sqlAnnotationTypes。add(Select。class);sqlAnnotationTypes。add(Insert。class);sqlAnnotationTypes。add(Update。class);sqlAnnotationTypes。add(Delete。class);sqlProviderAnnotationTypes。add(SelectProvider。class);sqlProviderAnnotationTypes。add(InsertProvider。class);sqlProviderAnnotationTypes。add(UpdateProvider。class);sqlProviderAnnotationTypes。add(DeleteProvider。class);}publicvoidparse(){Stringresourcetype。toString();if(!configuration。isResourceLoaded(resource)){loadXmlResource();configuration。addLoadedResource(resource);assistant。setCurrentNamespace(type。getName());parseCache();parseCacheRef();Method〔〕methodstype。getMethods();for(Methodmethod:methods){try{issue237if(!method。isBridge()){parseStatement(method);}}catch(IncompleteElementExceptione){configuration。addIncompleteMethod(newMethodResolver(this,method));}}}parsePendingMethods();}}
  javavoidparseStatement(Methodmethod){Classlt;?parameterTypeClassgetParameterType(method);LanguageDriverlanguageDrivergetLanguageDriver(method);SqlSourcesqlSourcegetSqlSourceFromAnnotations(method,parameterTypeClass,languageDriver);。。。}
  首先第一步是获取参数类型代码如下,如果mapper的入参数量大于1,则返回的就是ParamMap。class
  javaprivateClasslt;?getParameterType(Methodmethod){Classlt;?parameterTypenull;Classlt;?〔〕parameterTypesmethod。getParameterTypes();for(inti0;iparameterTypes。length;i){if(!RowBounds。class。isAssignableFrom(parameterTypes〔i〕)!ResultHandler。class。isAssignableFrom(parameterTypes〔i〕)){if(parameterTypenull){parameterTypeparameterTypes〔i〕;}else{issue135parameterTypeParamMap。class;}}}returnparameterType;}
  之后获取语言解析,没有指定就去找默认默认的是XMLLanguageDriver。class还是在Configuration类无参构造时添加进去的点我跳转到Configuration无参构造
  javaprivateLanguageDrivergetLanguageDriver(Methodmethod){Langlangmethod。getAnnotation(Lang。class);Classlt;?langClassnull;if(lang!null){langClasslang。value();}returnassistant。getLanguageDriver(langClass);}
  获取注解上的内容,以及封装sql就在这个方法
  javaprivateSqlSourcegetSqlSourceFromAnnotations(Methodmethod,Classlt;?parameterType,LanguageDriverlanguageDriver){try{获取是否存在Select,Insert。。。。Classlt;?extendsAnnotationsqlAnnotationTypegetSqlAnnotationType(method);获取是否存在SelectProvider,InsertProvider。。。Classlt;?extendsAnnotationsqlProviderAnnotationTypegetSqlProviderAnnotationType(method);if(sqlAnnotationType!null){if(sqlProviderAnnotationType!null){thrownewBindingException(YoucannotsupplybothastaticSQLandSqlProvidertomethodnamedmethod。getName());}AnnotationsqlAnnotationmethod。getAnnotation(sqlAnnotationType);获取注解上的值finalString〔〕strings(String〔〕)sqlAnnotation。getClass()。getMethod(value)。invoke(sqlAnnotation);返回sqlSource这个时候还没有进行填充值returnbuildSqlSourceFromStrings(strings,parameterType,languageDriver);}elseif(sqlProviderAnnotationType!null){AnnotationsqlProviderAnnotationmethod。getAnnotation(sqlProviderAnnotationType);returnnewProviderSqlSource(assistant。getConfiguration(),sqlProviderAnnotation);}returnnull;}catch(Exceptione){thrownewBuilderException(CouldnotfindvaluemethodonSQLannotation。Cause:e,e);}}
  此时的strings值还是selectfromuserwhereage{age}需要给替换为selectfromuserwhereage?
  javaprivateSqlSourcebuildSqlSourceFromStrings(String〔〕strings,Classlt;?parameterTypeClass,LanguageDriverlanguageDriver){finalStringBuildersqlnewStringBuilder();for(Stringfragment:strings){sql。append(fragment);sql。append();}returnlanguageDriver。createSqlSource(configuration,sql。toString()。trim(),parameterTypeClass);}
  默认的语言驱动是XMLLanguageDriver
  XMLLanguageDriver。java
  首先判断注解上的内容是否存在脚本,在mybatis官网,动态SQL下的script有使用案例,使得在注解中可以像在xml中使用ifwhere等标签
  javaOverridepublicSqlSourcecreateSqlSource(Configurationconfiguration,Stringscript,Classlt;?parameterType){issue3if(script。startsWith(lt;script)){XPathParserparsernewXPathParser(script,false,configuration。getVariables(),newXMLMapperEntityResolver());returncreateSqlSource(configuration,parser。evalNode(script),parameterType);}else{issue127scriptPropertyParser。parse(script,configuration。getVariables());TextSqlNodetextSqlNodenewTextSqlNode(script);判断是否为动态的sql就取决于使用的是{}还是{}当使用{}时就是动态sqlif(textSqlNode。isDynamic()){returnnewDynamicSqlSource(configuration,textSqlNode);}else{returnnewRawSqlSource(configuration,script,parameterType);}}}
  之后在RewSqlSource中对sql进行解析
  RewSqlSource。java
  javapublicRawSqlSource(Configurationconfiguration,Stringsql,Classlt;?parameterType){SqlSourceBuildersqlSourceParsernewSqlSourceBuilder(configuration);Classlt;?clazzparameterTypenull?Object。class:parameterType;sqlSourcesqlSourceParser。parse(sql,clazz,newHashMapString,Object());}
  解析完最终结果如下
  回到上个方法
  javavoidparseStatement(Methodmethod){Classlt;?parameterTypeClassgetParameterType(method);LanguageDriverlanguageDrivergetLanguageDriver(method);SqlSourcesqlSourcegetSqlSourceFromAnnotations(method,parameterTypeClass,languageDriver);从这里继续if(sqlSource!null){Optionsoptionsmethod。getAnnotation(Options。class);mappedStatementId类全限定方法名finalStringmappedStatementIdtype。getName()。method。getName();设置获取数据的大小IntegerfetchSizenull;设置此次查询超时时间Integertimeoutnull;https:blog。csdn。netking101125sarticledetails104167493StatementTypestatementTypeStatementType。PREPARED;resultSet结果类型FORWARDONLY光标只能向前移动ResultSetTyperesultSetTypeResultSetType。FORWARDONLY;设置sql类型,当前案例是SELECTSqlCommandTypesqlCommandTypegetSqlCommandType(method);booleanisSelectsqlCommandTypeSqlCommandType。SELECT;booleanflushCache!isSelect;booleanuseCacheisSelect;KeyGeneratorkeyGenerator;StringkeyPropertyid;StringkeyColumnnull;if(SqlCommandType。INSERT。equals(sqlCommandType)SqlCommandType。UPDATE。equals(sqlCommandType)){firstcheckforSelectKeyannotationthatoverrideseverythingelseSelectKeyselectKeymethod。getAnnotation(SelectKey。class);if(selectKey!null){keyGeneratorhandleSelectKeyAnnotation(selectKey,mappedStatementId,getParameterType(method),languageDriver);keyPropertyselectKey。keyProperty();}elseif(optionsnull){keyGeneratorconfiguration。isUseGeneratedKeys()?newJdbc3KeyGenerator():newNoKeyGenerator();}else{keyGeneratoroptions。useGeneratedKeys()?newJdbc3KeyGenerator():newNoKeyGenerator();keyPropertyoptions。keyProperty();keyColumnoptions。keyColumn();}}else{keyGeneratornewNoKeyGenerator();}if(options!null){flushCacheoptions。flushCache();useCacheoptions。useCache();fetchSizeoptions。fetchSize()1options。fetchSize()Integer。MINVALUE?options。fetchSize():null;issue348timeoutoptions。timeout()1?options。timeout():null;statementTypeoptions。statementType();resultSetTypeoptions。resultSetType();}StringresultMapIdnull;ResultMapresultMapAnnotationmethod。getAnnotation(ResultMap。class);if(resultMapAnnotation!null){String〔〕resultMapsresultMapAnnotation。value();StringBuildersbnewStringBuilder();for(StringresultMap:resultMaps){if(sb。length()0){sb。append(,);}sb。append(resultMap);}resultMapIdsb。toString();}elseif(isSelect){resultMapIdparseResultMap(method);}将参数传给小助手assistant。addMappedStatement(mappedStatementId,sqlSource,statementType,sqlCommandType,fetchSize,timeout,ParameterMapIDnull,parameterTypeClass,resultMapId,getReturnType(method),resultSetType,flushCache,useCache,TODOissue577false,keyGenerator,keyProperty,keyColumn,DatabaseIDnull,languageDriver,ResultSetsnull);}}
  添加mappedStatement
  javapublicMappedStatementaddMappedStatement(Stringid,SqlSourcesqlSource,StatementTypestatementType,SqlCommandTypesqlCommandType,IntegerfetchSize,Integertimeout,StringparameterMap,Classlt;?parameterType,StringresultMap,Classlt;?resultType,ResultSetTyperesultSetType,booleanflushCache,booleanuseCache,booleanresultOrdered,KeyGeneratorkeyGenerator,StringkeyProperty,StringkeyColumn,StringdatabaseId,LanguageDriverlang,StringresultSets){if(unresolvedCacheRef){thrownewIncompleteElementException(Cacherefnotyetresolved);}idapplyCurrentNamespace(id,false);booleanisSelectsqlCommandTypeSqlCommandType。SELECT;MappedStatement。BuilderstatementBuildernewMappedStatement。Builder(configuration,id,sqlSource,sqlCommandType)。resource(resource)。fetchSize(fetchSize)。timeout(timeout)。statementType(statementType)。keyGenerator(keyGenerator)。keyProperty(keyProperty)。keyColumn(keyColumn)。databaseId(databaseId)。lang(lang)。resultOrdered(resultOrdered)。resulSets(resultSets)。resultMaps(getStatementResultMaps(resultMap,resultType,id))。resultSetType(resultSetType)。flushCacheRequired(valueOrDefault(flushCache,!isSelect))。useCache(valueOrDefault(useCache,isSelect))。cache(currentCache);ParameterMapstatementParameterMapgetStatementParameterMap(parameterMap,parameterType,id);if(statementParameterMap!null){statementBuilder。parameterMap(statementParameterMap);}MappedStatementstatementstatementBuilder。build();这里将这个MappedStatement放入configurationconfiguration。addMappedStatement(statement);returnstatement;}
  将各种参数最后封装为一个MappedStatement,放入configuration中,这样一个addMapper的方法就结束了
  之后回到SqlSessionFactory的build中,执行重载的build
  javapublicSqlSessionFactorybuild(InputStreaminputStream,Stringenvironment,Propertiesproperties){XMLConfigBuilderparsernewXMLConfigBuilder(inputStream,environment,properties);回到这里returnbuild(parser。parse());}默认为DefaultSqlSessionFactorypublicSqlSessionFactorybuild(Configurationconfig){returnnewDefaultSqlSessionFactory(config);}
  DefaultSqlSessionFactory。java
  javapublicclassDefaultSqlSessionFactoryimplementsSqlSessionFactory{privatefinalConfigurationconfiguration;publicDefaultSqlSessionFactory(Configurationconfiguration){this。configurationconfiguration;}。。。。。}
  至此newSqlSessionFactoryBuilder()。build(inputStream)这段代码的解析环境和加载mapper就分析完了sqlSessionFactory
  回到主代码
  javapublicstaticvoidmain(String〔〕args)throwsException{Stringresourceorgmybatisconfig。xml;InputStreaminputStreamResources。getResourceAsStream(resource);SqlSessionFactorysqlSessionFactorynewSqlSessionFactoryBuilder()。build(inputStream);SqlSessionsessionsqlSessionFactory。openSession();。。}
  开始分析sqlSessionFactory。openSession();
  点进入发现有两个实现类,在上面的build中已经明确了创建的就是DefaultSqlSessionFactory
  DefaultSqlSessionFactory。java
  javaOverridepublicSqlSessionopenSession(){参数:执行器,默认为simple,每次关闭statementSimpleExecutorreturnopenSessionFromDataSource(configuration。getDefaultExecutorType(),null,false);}
  configuration。getDefaultExecutorType()在Configuration类中获取的是本身的一个属性,类型是一个ExecutorType的枚举,默认为SIMPE
  javaprivateSqlSessionopenSessionFromDataSource(ExecutorTypeexecType,TransactionIsolationLevellevel,booleanautoCommit){Transactiontxnull;try{获取配置类中的事务,datasource封装类finalEnvironmentenvironmentconfiguration。getEnvironment();通过配置的环境中获取事务工厂finalTransactionFactorytransactionFactorygetTransactionFactoryFromEnvironment(environment);创建对象datasource隔离等级是否自动提交txtransactionFactory。newTransaction(environment。getDataSource(),level,autoCommit);finalExecutorexecutorconfiguration。newExecutor(tx,execType);returnnewDefaultSqlSession(configuration,executor,autoCommit);}catch(Exceptione){closeTransaction(tx);mayhavefetchedaconnectionsoletscallclose()throwExceptionFactory。wrapException(Erroropeningsession。Cause:e,e);}finally{ErrorContext。instance()。reset();}}
  获取configuration中的事务工厂之后创建一个执行器
  Configuration。java
  javapublicExecutornewExecutor(Transactiontransaction,ExecutorTypeexecutorType){executorTypeexecutorTypenull?defaultExecutorType:executorType;executorTypeexecutorTypenull?ExecutorType。SIMPLE:executorType;Executorexecutor;if(ExecutorType。BATCHexecutorType){executornewBatchExecutor(this,transaction);}elseif(ExecutorType。REUSEexecutorType){executornewReuseExecutor(this,transaction);}else{executornewSimpleExecutor(this,transaction);}if(cacheEnabled){executornewCachingExecutor(executor);}executor(Executor)interceptorChain。pluginAll(executor);returnexecutor;}
  这里缓存是默认开启的,所以最后返回的是一个CachingExecutor包含着一个SimpleExecutor
  最后返回一个默认的DefaultSqlSession
  DefaultSqlSession。java
  javapublicDefaultSqlSession(Configurationconfiguration,Executorexecutor,booleanautoCommit){this。configurationconfiguration;this。executorexecutor;this。dirtyfalse;this。autoCommitautoCommit;}
  到这里session也创建完成了之后就是获取mapper执行查询了session。getMapper
  DefaultSqlSession。java
  javaOverridepublicTTgetMapper(ClassTtype){returnconfiguration。TgetMapper(type,this);}
  Configuration。java
  javapublicTTgetMapper(ClassTtype,SqlSessionsqlSession){returnmapperRegistry。getMapper(type,sqlSession);}
  MapperRegistry。java
  javapublicTTgetMapper(ClassTtype,SqlSessionsqlSession){finalMapperProxyFactoryTmapperProxyFactory(MapperProxyFactoryT)knownMappers。get(type);if(mapperProxyFactorynull){thrownewBindingException(TypetypeisnotknowntotheMapperRegistry。);}try{returnmapperProxyFactory。newInstance(sqlSession);}catch(Exceptione){thrownewBindingException(Errorgettingmapperinstance。Cause:e,e);}}
  MapperProxyFactory。java
  javaprotectedTnewInstance(MapperProxyTmapperProxy){return(T)Proxy。newProxyInstance(mapperInterface。getClassLoader(),newClass〔〕{mapperInterface},mapperProxy);}publicTnewInstance(SqlSessionsqlSession){finalMapperProxyTmapperProxynewMapperProxyT(sqlSession,mapperInterface,methodCache);returnnewInstance(mapperProxy);}
  MapperProxy。java
  动态代理来实现mapper的方法调用
  javapublicclassMapperProxyTimplementsInvocationHandler,Serializable{privatestaticfinallongserialVersionUID6424540398559729838L;privatefinalSqlSessionsqlSession;privatefinalClassTmapperInterface;privatefinalMapMethod,MapperMethodmethodCache;publicMapperProxy(SqlSessionsqlSession,ClassTmapperInterface,MapMethod,MapperMethodmethodCache){this。sqlSessionsqlSession;this。mapperInterfacemapperInterface;this。methodCachemethodCache;}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object〔〕args)throwsThrowable{if(Object。class。equals(method。getDeclaringClass())){try{returnmethod。invoke(this,args);}catch(Throwablet){throwExceptionUtil。unwrapThrowable(t);}}finalMapperMethodmapperMethodcachedMapperMethod(method);returnmapperMethod。execute(sqlSession,args);}privateMapperMethodcachedMapperMethod(Methodmethod){MapperMethodmapperMethodmethodCache。get(method);if(mapperMethodnull){mapperMethodnewMapperMethod(mapperInterface,method,sqlSession。getConfiguration());methodCache。put(method,mapperMethod);}returnmapperMethod;}}
  当我们执行userMapper。getUsers()的时候,通过动态代理进入invoke方法,之后获取缓存的方法,进入cachedMapperMethod
  先找是否已经创建过这个方法的封装类了,如果没有则去创建
  MapperMethod。java
  javapublicMapperMethod(Classlt;?mapperInterface,Methodmethod,Configurationconfig){this。commandnewSqlCommand(config,mapperInterface,method);this。methodnewMethodSignature(config,method);}
  MapperMethod。javaSqlCommand。java静态内部类
  javapublicSqlCommand(Configurationconfiguration,Classlt;?mapperInterface,Methodmethod){StringstatementNamemapperInterface。getName()。method。getName();MappedStatementmsnull;当程序走到这里的时候就会为trueif(configuration。hasStatement(statementName)){msconfiguration。getMappedStatement(statementName);}。。。。。。if(msnull){。。。。。}else{namems。getId();typems。getSqlCommandType();if(typeSqlCommandType。UNKNOWN){thrownewBindingException(Unknownexecutionmethodfor:name);}}}
  Configuration。java
  javapublicbooleanhasStatement(StringstatementName){returnhasStatement(statementName,true);}publicbooleanhasStatement(StringstatementName,booleanvalidateIncompleteStatements){if(validateIncompleteStatements){buildAllStatements();}主要这行returnmappedStatements。containsKey(statementName);}
  可能忘了它什么时候添加进去的了,在那个小助手中的addMappedStatement方法,最后的时候进行的添加点我跳转到addMappedStatement
  那么这里就直接走到最后的if了,将name和type赋值给SqlCommand,方法结束
  之后还有创建MethodSignature
  MapperMethod。javaMethodSignature。java静态内部类
  javapublicMethodSignature(Configurationconfiguration,Methodmethod){this。returnTypemethod。getReturnType();this。returnsVoidvoid。class。equals(this。returnType);this。returnsMany(configuration。getObjectFactory()。isCollection(this。returnType)this。returnType。isArray());this。mapKeygetMapKey(method);this。returnsMap(this。mapKey!null);是否存在Param注解this。hasNamedParametershasNamedParams(method);this。rowBoundsIndexgetUniqueParamIndex(method,RowBounds。class);this。resultHandlerIndexgetUniqueParamIndex(method,ResultHandler。class);this。paramsCollections。unmodifiableSortedMap(getParams(method,this。hasNamedParameters));}
  走完这一系列后回到MapperProxy。java中的invoke方法,最后执行mapperMethod。execute(sqlSession,args)
  MapperMethod。java
  javapublicObjectexecute(SqlSessionsqlSession,Object〔〕args){Objectresult;if(){。。。。。。。}elseif(SqlCommandType。SELECTcommand。getType()){if(method。returnsVoid()method。hasResultHandler()){executeWithResultHandler(sqlSession,args);resultnull;}elseif(method。returnsMany()){resultexecuteForMany(sqlSession,args);}elseif(method。returnsMap()){resultexecuteForMap(sqlSession,args);}else{Objectparammethod。convertArgsToSqlCommandParam(args);resultsqlSession。selectOne(command。getName(),param);}}elseif(SqlCommandType。FLUSHcommand。getType()){resultsqlSession。flushStatements();}else{thrownewBindingException(Unknownexecutionmethodfor:command。getName());}if(resultnullmethod。getReturnType()。isPrimitive()!method。returnsVoid()){thrownewBindingException(Mappermethodcommand。getName()attemptedtoreturnnullfromamethodwithaprimitivereturntype(method。getReturnType())。);}returnresult;}
  根据command的类型,当前这个案例是SELECT,进入后判断返回类型,当前是返回一个集合,returnsMany属性为ture,进入到resultexecuteForMany(sqlSession,args);
  javaprivateEObjectexecuteForMany(SqlSessionsqlSession,Object〔〕args){ListEresult;封装入参Objectparammethod。convertArgsToSqlCommandParam(args);这个为false,没有设置过逻辑分页if(method。hasRowBounds()){RowBoundsrowBoundsmethod。extractRowBounds(args);resultsqlSession。EselectList(command。getName(),param,rowBounds);}else{resultsqlSession。EselectList(command。getName(),param);}issue510Collectionsarrayssupportif(!method。getReturnType()。isAssignableFrom(result。getClass())){if(method。getReturnType()。isArray()){returnconvertToArray(result);}else{returnconvertToDeclaredCollection(sqlSession。getConfiguration(),result);}}returnresult;}
  javapublicObjectconvertArgsToSqlCommandParam(Object〔〕args){finalintparamCountparams。size();if(argsnullparamCount0){returnnull;}elseif(!hasNamedParametersparamCount1){returnargs〔params。keySet()。iterator()。next()〕;}else{finalMapString,ObjectparamnewParamMapObject();inti0;for(Map。EntryInteger,Stringentry:params。entrySet()){param。put(entry。getValue(),args〔entry。getKey()。intValue()〕);issue71,addparamnamesasparam1,param2。。。butensurebackwardcompatibilityfinalStringgenericParamNameparamString。valueOf(i1);if(!param。containsKey(genericParamName)){param。put(genericParamName,args〔entry。getKey()〕);}i;}returnparam;}}
  封装mapper入参,没有入参返回null,一个入参返回数组,多个入参返回ParamMap
  最终执行到resultsqlSession。selectList(command。getName(),param);
  DefaultSqlSession。java
  javapublicEListEselectList(Stringstatement,Objectparameter){returnthis。selectList(statement,parameter,RowBounds。DEFAULT);}OverridepublicEListEselectList(Stringstatement,Objectparameter,RowBoundsrowBounds){try{MappedStatementmsconfiguration。getMappedStatement(statement);returnexecutor。query(ms,wrapCollection(parameter),rowBounds,Executor。NORESULTHANDLER);}catch(Exceptione){throwExceptionFactory。wrapException(Errorqueryingdatabase。Cause:e,e);}finally{ErrorContext。instance()。reset();}}
  warpCollection方法对入参同一封装了一遍
  DefaultSqlSession。java
  javaprivateObjectwrapCollection(finalObjectobject){如果是集合if(objectinstanceofCollection){StrictMapObjectmapnewStrictMapObject();map。put(collection,object);if(objectinstanceofList){map。put(list,object);}returnmap;如果是数组}elseif(object!nullobject。getClass()。isArray()){StrictMapObjectmapnewStrictMapObject();map。put(array,object);returnmap;}returnobject;}
  之后进入executor。query()
  javaOverridepublicEListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException{BoundSqlboundSqlms。getBoundSql(parameter);CacheKeykeycreateCacheKey(ms,parameter,rowBounds,boundSql);returnquery(ms,parameter,rowBounds,resultHandler,key,boundSql);}
  第一步先获取该方法对应的sql,入参类型,入参参数
  ms。getBoundSql(parameter);
  之后是创建缓存key
  javaOverridepublicCacheKeycreateCacheKey(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,BoundSqlboundSql){if(closed){thrownewExecutorException(Executorwasclosed。);}CacheKeycacheKeynewCacheKey();cacheKey。update(ms。getId());cacheKey。update(Integer。valueOf(rowBounds。getOffset()));cacheKey。update(Integer。valueOf(rowBounds。getLimit()));cacheKey。update(boundSql。getSql());ListParameterMappingparameterMappingsboundSql。getParameterMappings();TypeHandlerRegistrytypeHandlerRegistryms。getConfiguration()。getTypeHandlerRegistry();mimicDefaultParameterHandlerlogicfor(inti0;iparameterMappings。size();i){ParameterMappingparameterMappingparameterMappings。get(i);if(parameterMapping。getMode()!ParameterMode。OUT){Objectvalue;StringpropertyNameparameterMapping。getProperty();if(boundSql。hasAdditionalParameter(propertyName)){valueboundSql。getAdditionalParameter(propertyName);}elseif(parameterObjectnull){valuenull;}elseif(typeHandlerRegistry。hasTypeHandler(parameterObject。getClass())){valueparameterObject;}else{MetaObjectmetaObjectconfiguration。newMetaObject(parameterObject);valuemetaObject。getValue(propertyName);}cacheKey。update(value);}}if(configuration。getEnvironment()!null){issue176cacheKey。update(configuration。getEnvironment()。getId());}returncacheKey;}
  创建CacheKey作为缓存key的封装类,根据以下参数进行生成keyMappedStatement的idrowBounds。getOffset()跳过条数rowBounds。getLimit()限制条数boundSql。getSql()要执行的sql语句
  之后遍历
  如果是数组就将每个元素取出然后执行doUpdate,否则直接执行
  CacheKey。java
  javaprivatevoiddoUpdate(Objectobject){intbaseHashCodeobjectnull?1:object。hashCode();count;checksumbaseHashCode;baseHashCodecount;hashcodemultiplierhashcodebaseHashCode;updateList。add(object);}
  最后走到query
  CachingExecutor。java
  javaOverridepublicEListEquery(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{Cachecachems。getCache();if(cache!null){flushCacheIfRequired(ms);if(ms。isUseCache()resultHandlernull){ensureNoOutParams(ms,parameterObject,boundSql);SuppressWarnings(unchecked)ListElist(ListE)tcm。getObject(cache,key);if(listnull){listdelegate。Equery(ms,parameterObject,rowBounds,resultHandler,key,boundSql);tcm。putObject(cache,key,list);issue578and116}returnlist;}}returndelegate。Equery(ms,parameterObject,rowBounds,resultHandler,key,boundSql);}
  因为是首次进入,没有缓存,直接到BaseExecutor。query方法
  BaseExecutor。java
  javaOverridepublicEListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{ErrorContext。instance()。resource(ms。getResource())。activity(executingaquery)。object(ms。getId());if(closed){thrownewExecutorException(Executorwasclosed。);}if(queryStack0ms。isFlushCacheRequired()){clearLocalCache();}ListElist;try{queryStack;listresultHandlernull?(ListE)localCache。getObject(key):null;if(list!null){handleLocallyCachedOutputParameters(ms,key,parameter,boundSql);}else{listqueryFromDatabase(ms,parameter,rowBounds,resultHandler,key,boundSql);}}finally{queryStack;}if(queryStack0){for(DeferredLoaddeferredLoad:deferredLoads){deferredLoad。load();}issue601deferredLoads。clear();if(configuration。getLocalCacheScope()LocalCacheScope。STATEMENT){issue482clearLocalCache();}}returnlist;}
  进入后判断如果queryStack0并且当前MappedStatement声明了需要清除缓存,则去清除缓存
  javaOptions(flushCacheOptions。FlushCachePolicy。TRUE)
  xmlselectidgetUserresultTypeuserflushCachetrue
  将queryStack,根据CacheKey获取对应缓存,如果没有则去查询数据库
  queryStack的作用
  BaseExecutor。java
  javaprivateEListEqueryFromDatabase(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{ListElist;localCache。putObject(key,EXECUTIONPLACEHOLDER);try{listdoQuery(ms,parameter,rowBounds,resultHandler,boundSql);}finally{localCache。removeObject(key);}localCache。putObject(key,list);if(ms。getStatementType()StatementType。CALLABLE){localOutputParameterCache。putObject(key,parameter);}returnlist;}
  进入doQuery
  SimpleExecutor。java
  javaOverridepublicEListEdoQuery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{Statementstmtnull;try{Configurationconfigurationms。getConfiguration();StatementHandlerhandlerconfiguration。newStatementHandler(wrapper,ms,parameter,rowBounds,resultHandler,boundSql);stmtprepareStatement(handler,ms。getStatementLog());returnhandler。Equery(stmt,resultHandler);}finally{closeStatement(stmt);}}
  创建statement执行类
  javapublicStatementHandlernewStatementHandler(Executorexecutor,MappedStatementmappedStatement,ObjectparameterObject,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql){StatementHandlerstatementHandlernewRoutingStatementHandler(executor,mappedStatement,parameterObject,rowBounds,resultHandler,boundSql);statementHandler(StatementHandler)interceptorChain。pluginAll(statementHandler);returnstatementHandler;}
  javapublicRoutingStatementHandler(Executorexecutor,MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql){switch(ms。getStatementType()){caseSTATEMENT:delegatenewSimpleStatementHandler(executor,ms,parameter,rowBounds,resultHandler,boundSql);break;casePREPARED:delegatenewPreparedStatementHandler(executor,ms,parameter,rowBounds,resultHandler,boundSql);break;caseCALLABLE:delegatenewCallableStatementHandler(executor,ms,parameter,rowBounds,resultHandler,boundSql);break;default:thrownewExecutorException(Unknownstatementtype:ms。getStatementType());}}
  这里使用的是{}MappedStatement的getStatementType返回为PREPARED最后返回PreparedStatementHandler
  之后解析prepareStatement
  javaprivateStatementprepareStatement(StatementHandlerhandler,LogstatementLog)throwsSQLException{Statementstmt;ConnectionconnectiongetConnection(statementLog);stmthandler。prepare(connection);handler。parameterize(stmt);returnstmt;}
  获取连接之后解析statement
  javaOverridepublicStatementprepare(Connectionconnection)throwsSQLException{Statementstatementnull;statementinstantiateStatement(connection);设置超时setStatementTimeout(statement);设置获取数据大小setFetchSize(statement);returnstatement;。。。。}
  PreparedStatementHandler。java
  javaOverrideprotectedStatementinstantiateStatement(Connectionconnection)throwsSQLException{StringsqlboundSql。getSql();if(mappedStatement。getKeyGenerator()instanceofJdbc3KeyGenerator){String〔〕keyColumnNamesmappedStatement。getKeyColumns();if(keyColumnNamesnull){returnconnection。prepareStatement(sql,PreparedStatement。RETURNGENERATEDKEYS);}else{returnconnection。prepareStatement(sql,keyColumnNames);}}elseif(mappedStatement。getResultSetType()!null){将会走到这里返回preparedStatementreturnconnection。prepareStatement(sql,mappedStatement。getResultSetType()。getValue(),ResultSet。CONCURREADONLY);}else{returnconnection。prepareStatement(sql);}}
  之后执行handler。parameterize(stmt);
  DefaultParameterHandler。java
  javaOverridepublicvoidsetParameters(PreparedStatementps){ErrorContext。instance()。activity(settingparameters)。object(mappedStatement。getParameterMap()。getId());ListParameterMappingparameterMappingsboundSql。getParameterMappings();if(parameterMappings!null){for(inti0;iparameterMappings。size();i){ParameterMappingparameterMappingparameterMappings。get(i);if(parameterMapping。getMode()!ParameterMode。OUT){Objectvalue;StringpropertyNameparameterMapping。getProperty();if(boundSql。hasAdditionalParameter(propertyName)){issue448askfirstforadditionalparamsvalueboundSql。getAdditionalParameter(propertyName);}elseif(parameterObjectnull){valuenull;判断入参类型是否有对应的解析类去找TYPEHANDLERMAP中是否存在对应的类型,当前demo的入参类型是int在TypeHandlerRegistry类的无参构造中已经将常用的基本数据类型和引用数据类型放入到了TYPEHANDLERMAP中}elseif(typeHandlerRegistry。hasTypeHandler(parameterObject。getClass())){valueparameterObject;}else{MetaObjectmetaObjectconfiguration。newMetaObject(parameterObject);valuemetaObject。getValue(propertyName);}这里获取的是IntegerTypeHandlerTypeHandlertypeHandlerparameterMapping。getTypeHandler();JdbcTypejdbcTypeparameterMapping。getJdbcType();if(valuenulljdbcTypenull){jdbcTypeconfiguration。getJdbcTypeForNull();}try{IntegerTypeHandler中的并没有重写setParameter进入BaseTypeHandler的setParametertypeHandler。setParameter(ps,i1,value,jdbcType);}catch(TypeExceptione){thrownewTypeException(Couldnotsetparametersformapping:parameterMapping。Cause:e,e);}catch(SQLExceptione){thrownewTypeException(Couldnotsetparametersformapping:parameterMapping。Cause:e,e);}}}}}
  BaseTypeHandler。java
  javaOverridepublicvoidsetParameter(PreparedStatementps,inti,Tparameter,JdbcTypejdbcType)throwsSQLException{if(parameternull){if(jdbcTypenull){thrownewTypeException(JDBCrequiresthattheJdbcTypemustbespecifiedforallnullableparameters。);}try{ps。setNull(i,jdbcType。TYPECODE);}catch(SQLExceptione){thrownewTypeException(ErrorsettingnullforparameteriwithJdbcTypejdbcType。TrysettingadifferentJdbcTypeforthisparameteroradifferentjdbcTypeForNullconfigurationproperty。Cause:e,e);}}else{try{进入这里setNonNullParameter(ps,i,parameter,jdbcType);}catch(Exceptione){thrownewTypeException(ErrorsettingnonnullforparameteriwithJdbcTypejdbcType。TrysettingadifferentJdbcTypeforthisparameteroradifferentconfigurationproperty。Cause:e,e);}}}
  javapublicclassIntegerTypeHandlerextendsBaseTypeHandlerInteger{OverridepublicvoidsetNonNullParameter(PreparedStatementps,inti,Integerparameter,JdbcTypejdbcType)throwsSQLException{ps。setInt(i,parameter);}}
  最后回到SimpleExecutor类中的prepareStatement方法,返回PreparedStatement
  javaOverridepublicEListEdoQuery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{Statementstmtnull;try{Configurationconfigurationms。getConfiguration();StatementHandlerhandlerconfiguration。newStatementHandler(wrapper,ms,parameter,rowBounds,resultHandler,boundSql);stmtprepareStatement(handler,ms。getStatementLog());获取返回的stmt,进行查询returnhandler。Equery(stmt,resultHandler);}finally{closeStatement(stmt);}}
  PreparedStatementHandler。java
  javaOverridepublicEListEquery(Statementstatement,ResultHandlerresultHandler)throwsSQLException{PreparedStatementps(PreparedStatement)statement;ps。execute();returnresultSetHandler。EhandleResultSets(ps);}
  DefaultResultSetHandler。java
  javaOverridepublicListObjecthandleResultSets(Statementstmt)throwsSQLException{ErrorContext。instance()。activity(handlingresults)。object(mappedStatement。getId());finalListObjectmultipleResultsnewArrayListObject();intresultSetCount0;先将结果进行封装ResultSetWrapperrswgetFirstResultSet(stmt);。。。。。}
  javaprivateResultSetWrappergetFirstResultSet(Statementstmt)throwsSQLException{ResultSetrsstmt。getResultSet();while(rsnull){moveforwardtogetthefirstresultsetincasethedriverdoesntreturntheresultsetasthefirstresult(HSQLDB2。1)if(stmt。getMoreResults()){rsstmt。getResultSet();}else{if(stmt。getUpdateCount()1){nomoreresults。Mustbenoresultsetbreak;}}}returnrs!null?newResultSetWrapper(rs,configuration):null;}
  ResultSetWrapper。java
  将结果对应的查询列名,数据库列类型,类名
  javapublicResultSetWrapper(ResultSetrs,Configurationconfiguration)throwsSQLException{super();this。typeHandlerRegistryconfiguration。getTypeHandlerRegistry();this。resultSetrs;finalResultSetMetaDatametaDatars。getMetaData();finalintcolumnCountmetaData。getColumnCount();for(inti1;icolumnCount;i){columnNames。add(configuration。isUseColumnLabel()?metaData。getColumnLabel(i):metaData。getColumnName(i));jdbcTypes。add(JdbcType。forCode(metaData。getColumnType(i)));classNames。add(metaData。getColumnClassName(i));}}
  回到DefaultResultSetHandler的handleResultSets方法
  javaOverridepublicListObjecthandleResultSets(Statementstmt)throwsSQLException{ErrorContext。instance()。activity(handlingresults)。object(mappedStatement。getId());finalListObjectmultipleResultsnewArrayListObject();intresultSetCount0;ResultSetWrapperrswgetFirstResultSet(stmt);ListResultMapresultMapsmappedStatement。getResultMaps();intresultMapCountresultMaps。size();检查查询结果是否指定对应的类或resultMapvalidateResultMapsCount(rsw,resultMapCount);while(rsw!nullresultMapCountresultSetCount){ResultMapresultMapresultMaps。get(resultSetCount);处理结果,当前demo是封装为UserhandleResultSet(rsw,resultMap,multipleResults,null);rswgetNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount;}。。。。。。。}
  javaprivatevoidhandleResultSet(ResultSetWrapperrsw,ResultMapresultMap,ListObjectmultipleResults,ResultMappingparentMapping)throwsSQLException{try{if(parentMapping!null){handleRowValues(rsw,resultMap,null,RowBounds。DEFAULT,parentMapping);}else{是否存在自定义的结果处理器if(resultHandlernull){我们没有设置,则使用默认结果处理DefaultResultHandlerdefaultResultHandlernewDefaultResultHandler(objectFactory);处理一行数据handleRowValues(rsw,resultMap,defaultResultHandler,rowBounds,null);multipleResults。add(defaultResultHandler。getResultList());}else{handleRowValues(rsw,resultMap,resultHandler,rowBounds,null);}}}finally{issue228(closeresultsets)closeResultSet(rsw。getResultSet());}}
  javaprivatevoidhandleRowValues(ResultSetWrapperrsw,ResultMapresultMap,ResultHandlerlt;?resultHandler,RowBoundsrowBounds,ResultMappingparentMapping)throwsSQLException{如果设置了resultMap,我们没有设置,走elseif(resultMap。hasNestedResultMaps()){ensureNoRowBounds();checkResultHandler();handleRowValuesForNestedResultMap(rsw,resultMap,resultHandler,rowBounds,parentMapping);}else{handleRowValuesForSimpleResultMap(rsw,resultMap,resultHandler,rowBounds,parentMapping);}}
  javaprivatevoidhandleRowValuesForSimpleResultMap(ResultSetWrapperrsw,ResultMapresultMap,ResultHandlerlt;?resultHandler,RowBoundsrowBounds,ResultMappingparentMapping)throwsSQLException{DefaultResultContextObjectresultContextnewDefaultResultContextObject();根据rowBounds跳过行数skipRows(rsw。getResultSet(),rowBounds);判断是否应该处理更多移动resultSet光标并且返回是否还有更多数据while(shouldProcessMoreRows(resultContext,rowBounds)rsw。getResultSet()。next()){ResultMapdiscriminatedResultMapresolveDiscriminatedResultMap(rsw。getResultSet(),resultMap,null);填充值方法ObjectrowValuegetRowValue(rsw,discriminatedResultMap);storeObject(resultHandler,resultContext,rowValue,parentMapping,rsw。getResultSet());}}
  判断当前结果上下文是否已经关闭,并且判断当前结果集合总数是否小于rowBounds规定的限制
  javaprivatebooleanshouldProcessMoreRows(ResultContextlt;?context,RowBoundsrowBounds)throwsSQLException{return!context。isStopped()context。getResultCount()rowBounds。getLimit();}
  javaprivateObjectgetRowValue(ResultSetWrapperrsw,ResultMapresultMap)throwsSQLException{finalResultLoaderMaplazyLoadernewResultLoaderMap();创建实体对象ObjectresultObjectcreateResultObject(rsw,resultMap,lazyLoader,null);if(resultObject!null!typeHandlerRegistry。hasTypeHandler(resultMap。getType())){finalMetaObjectmetaObjectconfiguration。newMetaObject(resultObject);booleanfoundValues!resultMap。getConstructorResultMappings()。isEmpty();if(shouldApplyAutomaticMappings(resultMap,false)){foundValuesapplyAutomaticMappings(rsw,resultMap,metaObject,null)foundValues;}foundValuesapplyPropertyMappings(rsw,resultMap,metaObject,lazyLoader,null)foundValues;foundValueslazyLoader。size()0foundValues;resultObjectfoundValues?resultObject:null;returnresultObject;}returnresultObject;}
  javaprivateObjectcreateResultObject(ResultSetWrapperrsw,ResultMapresultMap,ResultLoaderMaplazyLoader,StringcolumnPrefix)throwsSQLException{finalListClasslt;?constructorArgTypesnewArrayListClasslt;?();finalListObjectconstructorArgsnewArrayListObject();创建对象finalObjectresultObjectcreateResultObject(rsw,resultMap,constructorArgTypes,constructorArgs,columnPrefix);。。。。。returnresultObject;}
  javaprivateObjectcreateResultObject(ResultSetWrapperrsw,ResultMapresultMap,ListClasslt;?constructorArgTypes,ListObjectconstructorArgs,StringcolumnPrefix)throwsSQLException{finalClasslt;?resultTyperesultMap。getType();获取类的元数据例如类存在几个属性,几个getter几个setter等等类的基本信息finalMetaClassmetaTypeMetaClass。forClass(resultType,reflectorFactory);判断用户是否指明了使用ConstructorArgs来填充参数,当前demo没有,所以这个集合为空finalListResultMappingconstructorMappingsresultMap。getConstructorResultMappings();判断该类是否存在对应的类型处理器if(typeHandlerRegistry。hasTypeHandler(resultType)){returncreatePrimitiveResultObject(rsw,resultMap,columnPrefix);判断是否需要通过有参构造方式填充}elseif(!constructorMappings。isEmpty()){returncreateParameterizedResultObject(rsw,resultType,constructorMappings,constructorArgTypes,constructorArgs,columnPrefix);判断如果当前类hi接口,或者存在默认构造}elseif(resultType。isInterface()metaType。hasDefaultConstructor()){里面最终使用class。newInstance()返回对象returnobjectFactory。create(resultType);}elseif(shouldApplyAutomaticMappings(resultMap,false)){returncreateByConstructorSignature(rsw,resultType,constructorArgTypes,constructorArgs,columnPrefix);}thrownewExecutorException(DonotknowhowtocreateaninstanceofresultType);}
  走完这些就会回到DefaultResultSetHandler中的getRowValue方法,其中还有些方法没有对类进行实际修改就省略了
  javaprivateObjectgetRowValue(ResultSetWrapperrsw,ResultMapresultMap)throwsSQLException{finalResultLoaderMaplazyLoadernewResultLoaderMap();回到这里ObjectresultObjectcreateResultObject(rsw,resultMap,lazyLoader,null);是否有对应的类型处理器if(resultObject!null!typeHandlerRegistry。hasTypeHandler(resultMap。getType())){finalMetaObjectmetaObjectconfiguration。newMetaObject(resultObject);获取是否通过构造填充参数,当前demo为空取反falsebooleanfoundValues!resultMap。getConstructorResultMappings()。isEmpty();判断是否使用了自动映射https:mybatis。orgmybatis3zhconfiguration。htmlsettingssetting设置autoMappingBehaviorif(shouldApplyAutomaticMappings(resultMap,false)){应用自动映射foundValuesapplyAutomaticMappings(rsw,resultMap,metaObject,null)foundValues;}应用已经映射好的foundValuesapplyPropertyMappings(rsw,resultMap,metaObject,lazyLoader,null)foundValues;判断懒加载或当前填充完成foundValueslazyLoader。size()0foundValues;resultObjectfoundValues?resultObject:null;returnresultObject;}returnresultObject;}
  javaprivatebooleanapplyAutomaticMappings(ResultSetWrapperrsw,ResultMapresultMap,MetaObjectmetaObject,StringcolumnPrefix)throwsSQLException{创建自动映射的列ListUnMappedColumAutoMappingautoMappingcreateAutomaticMappings(rsw,resultMap,metaObject,columnPrefix);booleanfoundValuesfalse;if(autoMapping。size()0){for(UnMappedColumAutoMappingmapping:autoMapping){finalObjectvaluemapping。typeHandler。getResult(rsw。getResultSet(),mapping。column);issue377,callsetteronnulls如果获取的值不为空,或者允许设置空值if(value!nullconfiguration。isCallSettersOnNulls()){如果value不为空或者值不是基本数据类型if(value!null!mapping。primitive){反射设置值metaObject。setValue(mapping。property,value);}foundValuestrue;}}}returnfoundValues;}
  javaprivateListUnMappedColumAutoMappingcreateAutomaticMappings(ResultSetWrapperrsw,ResultMapresultMap,MetaObjectmetaObject,StringcolumnPrefix)throwsSQLException{finalStringmapKeyresultMap。getId():columnPrefix;ListUnMappedColumAutoMappingautoMappingautoMappingsCache。get(mapKey);if(autoMappingnull){autoMappingnewArrayListUnMappedColumAutoMapping();获取没有映射的字段名称finalListStringunmappedColumnNamesrsw。getUnmappedColumnNames(resultMap,columnPrefix);for(StringcolumnName:unmappedColumnNames){StringpropertyNamecolumnName;。。。根据名称获取对应实体类的属性名isMapUnderscoreToCamelCase是否开启下划线转到驼峰finalStringpropertymetaObject。findProperty(propertyName,configuration。isMapUnderscoreToCamelCase());if(property!nullmetaObject。hasSetter(property)){获取对应属性的set方法的类型finalClasslt;?propertyTypemetaObject。getSetterType(property);寻找是否有对应的类型转换if(typeHandlerRegistry。hasTypeHandler(propertyType)){finalTypeHandlerlt;?typeHandlerrsw。getTypeHandler(propertyType,columnName);autoMapping。add(newUnMappedColumAutoMapping(columnName,property,typeHandler,propertyType。isPrimitive()));}}}放入缓存autoMappingsCache。put(mapKey,autoMapping);}returnautoMapping;}
  之后就是获取ResultSet中的值填充到实体类中
  BaseTypeHandler。java
  javaOverridepublicTgetResult(ResultSetrs,StringcolumnName)throwsSQLException{Tresult;try{resultgetNullableResult(rs,columnName);}catch(Exceptione){thrownewResultMapException(ErrorattemptingtogetcolumncolumnNamefromresultset。Cause:e,e);}if(rs。wasNull()){returnnull;}else{returnresult;}}
  IntegerTypeHandler。java
  javaOverridepublicIntegergetNullableResult(ResultSetrs,StringcolumnName)throwsSQLException{returnrs。getInt(columnName);}
  这个时候对象已经填充完一个了
  之后调用storeObject
  DefaultResultSetHandler。java
  javaprivatevoidstoreObject(ResultHandlerlt;?resultHandler,DefaultResultContextObjectresultContext,ObjectrowValue,ResultMappingparentMapping,ResultSetrs)throwsSQLException{if(parentMapping!null){linkToParents(rs,parentMapping,rowValue);}else{进入这里callResultHandler(resultHandler,resultContext,rowValue);}}
  javaprivatevoidcallResultHandler(ResultHandlerlt;?resultHandler,DefaultResultContextObjectresultContext,ObjectrowValue){将对象存储到resultContextresultContext。nextResultObject(rowValue);((ResultHandlerObject)resultHandler)。handleResult(resultContext);}
  DefaultResultContext。java
  javapublicvoidnextResultObject(TresultObject){resultCount;this。resultObjectresultObject;}
  之后执行handleResult
  javapublicclassDefaultResultHandlerimplementsResultHandlerObject{privatefinalListObjectlist;。。。。OverridepublicvoidhandleResult(ResultContextlt;?extendsObjectcontext){list。add(context。getResultObject());}publicListObjectgetResultList(){returnlist;}}
  至此一个对象的创建填充就完成了,之后回到DefaultResultSetHandler的handleRowValuesForSimpleResultMap方法继续下一个对象的循环
  javaprivatevoidhandleRowValuesForSimpleResultMap(ResultSetWrapperrsw,ResultMapresultMap,ResultHandlerlt;?resultHandler,RowBoundsrowBounds,ResultMappingparentMapping)throwsSQLException{DefaultResultContextObjectresultContextnewDefaultResultContextObject();skipRows(rsw。getResultSet(),rowBounds);一直把result的数据循环完while(shouldProcessMoreRows(resultContext,rowBounds)rsw。getResultSet()。next()){ResultMapdiscriminatedResultMapresolveDiscriminatedResultMap(rsw。getResultSet(),resultMap,null);ObjectrowValuegetRowValue(rsw,discriminatedResultMap);storeObject(resultHandler,resultContext,rowValue,parentMapping,rsw。getResultSet());}}
  然后方法一直返回到DefaultResultSetHandler的handleResultSets方法
  javaOverridepublicListObjecthandleResultSets(Statementstmt)throwsSQLException{ErrorContext。instance()。activity(handlingresults)。object(mappedStatement。getId());finalListObjectmultipleResultsnewArrayListObject();intresultSetCount0;ResultSetWrapperrswgetFirstResultSet(stmt);ListResultMapresultMapsmappedStatement。getResultMaps();intresultMapCountresultMaps。size();validateResultMapsCount(rsw,resultMapCount);while(rsw!nullresultMapCountresultSetCount){ResultMapresultMapresultMaps。get(resultSetCount);回到这里handleResultSet(rsw,resultMap,multipleResults,null);判断是否存在下一个resultSet一般一个statement只会返回一个resultSetrswgetNextResultSet(stmt);cleanUpAfterHandlingResultSet();resultSetCount;}。。。。。。returncollapseSingleResultList(multipleResults);}
  javaprivateListObjectcollapseSingleResultList(ListObjectmultipleResults){returnmultipleResults。size()1?(ListObject)multipleResults。get(0):multipleResults;}
  最后,返回这个list,整个方法结束

你什么习惯,就什么病点击上方蓝字关注,晴风每天和你一起剖析情感知识。文晴风图网络(若有侵权,请联系删除)不知道你有没有发现这样的一个现象,就是我们的习惯决定了一切,我们的习惯好坏决定了我们人生的好坏。不算情书题记岁月最终浓缩为一段文字,留一节給自己。文学的疯子,纯净的小孩!你智慧,你激情,你用分行的文字抚慰我文字的分行,那种异地的呼应灵动的悲悯的呼应,让我不再孤单。喜欢你的现代诗,那些小学生作文我的小姨火了,小姨看后血压飙升,妈妈却笑弯了腰素质教育下,小学生们的想法也更加个性,常常会有诸多的奇思妙想来打败大人,再加上小学生接触的网络较多,各种网络热梗也被他们所熟练运用。小学生的想法是走在潮流前端的,尤其是网络词汇还能十月的风十月的风,仅仅是十月的风,送去疫影,留下安宁。告离了秋日,刚刚入冬。它独具魅力,不像春天般和煦柔情,也不像寒冬般凌厉凶猛。打个比方,它象哲人一样不偏不倚,不激不厉,自有它中和上的秉别拿自己太当人,别拿别人不当人点击关注,让我们共同成长有句话说得好做人太过精明,只会给自己挖下一个又一个坑做事太爱算计,只会弄丢了良心。把别人当傻瓜的人,才是真正的傻瓜。别把别人不当人,别把自己太当人。真正的精戚薇公开二胎vlog,产前体重曝光,lucky初见弟弟超激动10月27日,在播出的综艺节目披荆斩棘里,李承铉开心官宣了二胎得子的好消息,好兄弟们纷纷为他和戚薇送上了祝福。二度升级当奶爸,李承铉仍然激动万分,他直言我有了一个钢铁男子,喜悦之情IG三C重聚,买阿水组全神班?LPL转会新瓜来袭,UZI可惜了2022电竞季前言英雄联盟S12世界赛已经过半,最终LPL四支战队里面,只有JDG进入了半决赛,形成了和S11同样的局面,那就是LPL独苗战队被LCK三支队伍围堵,而且和之前相比,千万粉网红官宣恋情!紧搂女友秀恩爱,女方身材火辣颜值不输明星饿了吗?戳右边关注我们,每天给您送上最新出炉的娱乐硬核大餐!10月27日,在全网拥有近4000万粉丝的网红回忆专用小马甲突然官宣恋情,不到半小时点赞评论破万,引发热议。作为初代萌宠美民主党中期选举预期不佳布隆伯格再次大手笔捐款中新网10月27日电据美国国会山报26日报道,随着中期选举临近,亿万富翁前纽约市长布隆伯格向佩洛西支持的民主党超级政治行动委员会(PAC)追加1000万美元(约合7173万元人民币人口普查结果显示,移民占加拿大人口近四分之一据加拿大环球邮报网站27日报道,根据新发布的2021年人口普查结果,移民现今在加拿大人口中所占比例达到史上最高的23,主要原因是加拿大为解决本国劳动力短缺而努力吸引移民,主要是年轻RedmiNote12探索版测评210W快充和2亿像素是不是噱头?在刚刚结束的发布会上,官方宣布RedmiNote系列全球销量超过3亿。不到半年,这个爆款系列就迎来了全新一代,时间点又恰逢双11,足以预见此时有多少双眼睛在盯着RedmiNote1
健脑食品的种类1。豆类,对于处在大脑发育时期的孩子来说,富含植物蛋白质很高的各种豆类是必不可少的营养食品,如黄豆,花生米,豌豆等。豆类有增强记忆延缓脑细胞衰老的作用。2。蛋类,多吃蛋类,会让孩子燕麦米对于减脂的3大好处,你懂吗?燕麦米早上好,健康饮食每天聊一聊,感谢你阅读文章,我是华二少。我们在生活中,食物里适当的增加燕麦米的摄入对于减脂至少有3个好处首先,它对于减脂是有利的。燕麦米含有丰富的蛋白质,每百黄花菜都凉了!京媒曝艾伦加盟北控男篮,张敬松迎来救世主吗?北京时间1月13日,根据天天体育的爆料,北控男篮第三外援艾伦已经抵达球队,各方面手续没有特殊的情况,他将会出战浙江男篮的系列赛。艾伦上赛季效力于VTB联赛帕尔马队,作为双能后卫,身马拉多纳86年世界杯和梅西2022世界杯到底谁发挥的更好?阿根廷获得世界杯冠军,很多人又用马拉多纳来贬低梅西,说老马才是真正的一个人的世界杯,而梅西是靠点球获得的世界杯,下面就来说老马86年和梅西2022世界杯到底谁发挥的更好。先来看看阵世界杯上的点球故事梅西和阿根廷领跑个人及国家队榜单梅西决赛中主罚本届杯赛个人的第五粒点球莱昂内尔梅西在对阵法国的决赛中将阿根廷带上了世界杯的宝座,并同时在万众瞩目中打进了个人在2022年世界杯的第六个和第七个进球。其中第一个进球为不要跟这个世界格格不入有一位网民慨然撰文哀叹我是一个传统意识非常强的人,经虽然年轻,但是总感到自己和现在的经济社会格格不入。我向往古人的那种侠义豪爽和忠肝义胆,但在现代人身上早已找不到这些优秀的品质了,2022年全球PC市场联想第一,苹果第四近日市场调研机构IDC公布了2022年全球PC市场TOP5数据1)联想,出货量为6800万台,市场份额为23。32)惠普,出货量为5530万台,市场份额为18。93)戴尔,出货量为我国情趣用品行业分析全渠道发展是情趣用品行业大趋势1情趣用品行业定义分类及特点情趣用品,通常为依靠物理作用,帮助成年人满足性爱需求的辅助类性用品,以达到改善或提高性功能的目的。情趣用品早在中国古代就已经开始流行,基本为古代女性闺房保市场主体!财政这样支持企业纾困发展秒懂财政视频加载中(保市场主体!财政这样支持企业纾困发展秒懂财政,时长共1分19秒)保市场主体!财政这样支持企业纾困发展秒懂财政市场主体是实体经济发展的主力军。保市场主体就是保社会生产力,星访谈文评组重庆大学任怡璇保持表达的勇气,发出真实与真诚之声重庆大学学生任怡璇。红网时刻长沙1月13日讯(记者钟星月张瑜)由湖南红网新媒体集团主办的第八届全国大学生评论之星选拔赛10月月赛结果今日出炉。来自重庆大学的任怡璇,凭借疫情沟通需要仲恺高新区经发局副局长叶岸科今年内启动建设仲恺中小企业集中发展区日前,2022年广东省专精特新中小企业名单公示,仲恺高新区共144家企业新入选。至此,仲恺高新区共2家国家级重点小巨人企业,13家专精特新小巨人企业,省级专精特新中小企业存量达21
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网