有网站怎样做推广,排名优化网站建设,校园二手市场网站建设,网站技术维护费我们之前介绍过MappedStatement表示的是XML中的一个SQL。类当中的很多字段都是SQL中对应的属性。我们先来了解一下这个类的属性#xff1a; public final class MappedStatement {private String resource;private Configuration configuration;//sql的IDprivate String id;//…我们之前介绍过MappedStatement表示的是XML中的一个SQL。类当中的很多字段都是SQL中对应的属性。我们先来了解一下这个类的属性 public final class MappedStatement {private String resource;private Configuration configuration;//sql的IDprivate String id;//尝试影响驱动程序每次批量返回的结果行数和这个设置值相等private Integer fetchSize;//SQL超时时间private Integer timeout;//Statement的类型STATEMENT/PREPARE/CALLABLEprivate StatementType statementType;//结果集类型FORWARD_ONLY/SCROLL_SENSITIVE/SCROLL_INSENSITIVE private ResultSetType resultSetType;//表示解析出来的SQLprivate SqlSource sqlSource;//缓存private Cache cache;//已废弃private ParameterMap parameterMap;//对应的ResultMapprivate ListResultMap resultMaps;private boolean flushCacheRequired;private boolean useCache;private boolean resultOrdered;//SQL类型INSERT/SELECT/DELETEprivate SqlCommandType sqlCommandType;//和SELECTKEY标签有关private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;private boolean hasNestedResultMaps;//数据库ID用来区分不同环境private String databaseId;private Log statementLog;private LanguageDriver lang;//多结果集时private String[] resultSets;MappedStatement() {// constructor disabled}...} 对一些重要的字段我都增加了备注方便理解。其中真正表示SQL的字段是SqlSource这个对象。 SqlSource接口很简单只有一个getBound方法: public interface SqlSource {BoundSql getBoundSql(Object parameterObject);} 它有很多实现需要我们重点关注的是StaticSqlSourceRawSqlSource和DynamicSqlSource。在正式学习他们前我们先了解一下Mybatis动态SQL和静态SQL的区别。 动态SQL表示这个SQL节点中含有${}或是其他动态的标签比如iftrimforeachchoosebind节点等需要在运行时根据传入的条件才能确定SQL因此对于动态SQL的MappedStatement的解析过程应该是在运行时。 而静态SQL是不含以上这个节点的SQL能直接解析得到含有占位符形式的SQL语句而不需要根据传入的条件确定SQL因此可以在加载时就完成解析。所在在执行效率上要高于动态SQL。 而DynamicSqlSource和RawSqlSource就分别对应了动态SQL和静态SQL它们都封装了StaticSqlSource。 我们先从简单的入手了解静态SQL的解析过程。 ?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapperPUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtdmapper namespaceorg.apache.ibatis.domain.blog.mappers.AuthorMapperselect idselectAllAuthors resultTypeorg.apache.ibatis.domain.blog.Authorselect * from author/select/mapper 这是我们要解析的XML文件mapper节点下只有一个select节点。 public class XmlMapperBuilderTest {Testpublic void shouldSuccessfullyLoadXMLMapperFile() throws Exception {Configuration configuration new Configuration();String resource org/apache/ibatis/builder/AuthorMapper.xml;InputStream inputStream Resources.getResourceAsStream(resource);XMLMapperBuilder builder new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());builder.parse();inputStream.close();}
} 这是我们测试解析过程的代码。我们可以看到解析是由XMLMapperBuilder开始的。我们先了解一下它的字段 public class XMLMapperBuilder extends BaseBuilder {//用来解析XMLprivate final XPathParser parser;//再解析完成后用解析所得的属性来帮助创建各个对象private final MapperBuilderAssistant builderAssistant;//保存SQL节点private final MapString, XNode sqlFragments;//...
} 它还从父类中继承了configuration配置对象typeAliasRegistry类型别名注册器和typeHandlerRegistry类型处理器注册器。 接下来看一下它的parse方法 public void parse() {//判断是否已经加载过资源 if (!configuration.isResourceLoaded(resource)) {//从mapper根节点开始解析configurationElement(parser.evalNode(/mapper));//将该资源添加到为已经加载过的缓存中configuration.addLoadedResource(resource);//将解析的SQL和接口中的方法绑定bindMapperForNamespace();}//对一些未完成解析的节点再解析parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();} 主要的解析过程在configurationElement中 private void configurationElement(XNode context) {try {//解析mapper的namespace属性并设置String namespace context.getStringAttribute(namespace);if (namespace null || namespace.equals()) {throw new BuilderException(Mappers namespace cannot be empty);}builderAssistant.setCurrentNamespace(namespace);//解析cache-ref节点它有一个namespace属性表示引用该命名空间下的缓存cacheRefElement(context.evalNode(cache-ref));//解析cache节点可以设置缓存类型和属性或是指定自定义的缓存cacheElement(context.evalNode(cache));//已废弃不再使用 parameterMapElement(context.evalNodes(/mapper/parameterMap));//解析resultMap节点resultMapElements(context.evalNodes(/mapper/resultMap));//解析SQL节点SQL节点可以使一些SQL片段被复用 sqlElement(context.evalNodes(/mapper/sql));//解析SQL语句select|insert|update|delete节点 buildStatementFromContext(context.evalNodes(select|insert|update|delete));} catch (Exception e) {throw new BuilderException(Error parsing Mapper XML. The XML location is resource . Cause: e, e);}} 我们关注SQL语句的解析过程上述buildStatementFromContext(ListXNode)方法会增加dateBaseId的参数然后调用另一个重载方法 private void buildStatementFromContext(ListXNode list, String requiredDatabaseId) {//遍历XNode节点for (XNode context : list) {//为每个节点创建XMLStatementBuilder对象final XMLStatementBuilder statementParser new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {//解析NodestatementParser.parseStatementNode();} catch (IncompleteElementException e) {//对不能完全解析的节点添加到incompleteStatement在parsePendingStatements方法中再解析configuration.addIncompleteStatement(statementParser);}}} 先看看XMLStatementBuilder对象 public class XMLStatementBuilder extends BaseBuilder {private final MapperBuilderAssistant builderAssistant;private final XNode context;private final String requiredDatabaseId;// ...
} View Code 含有的字段相对简单不再具体解释。直接看parseStatementNode方法 public void parseStatementNode() {//获取idString id context.getStringAttribute(id);String databaseId context.getStringAttribute(databaseId);//验证databaseId是否匹配if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}Integer fetchSize context.getIntAttribute(fetchSize);Integer timeout context.getIntAttribute(timeout);//已废弃String parameterMap context.getStringAttribute(parameterMap);//参数类型将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数默认值为 unset。String parameterType context.getStringAttribute(parameterType);Class? parameterTypeClass resolveClass(parameterType);//结果类型外部 resultMap 的命名引用。String resultMap context.getStringAttribute(resultMap);//结果类型表示从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形那应该是集合可以包含的类型而不能是集合本身。不能和resultMap同时使用。String resultType context.getStringAttribute(resultType);String lang context.getStringAttribute(lang);LanguageDriver langDriver getLanguageDriver(lang);Class? resultTypeClass resolveClass(resultType);//结果集类型FORWARD_ONLYSCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个默认值为 unset 依赖驱动。String resultSetType context.getStringAttribute(resultSetType);//STATEMENTPREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 StatementPreparedStatement 或 CallableStatement默认值PREPARED。StatementType statementType StatementType.valueOf(context.getStringAttribute(statementType, StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum resolveResultSetType(resultSetType);String nodeName context.getNode().getNodeName();//SQLCommand类型SqlCommandType sqlCommandType SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect sqlCommandType SqlCommandType.SELECT;//flushCache在执行语句时表示是否刷新缓存boolean flushCache context.getBooleanAttribute(flushCache, !isSelect);//是否对该语句进行二级缓存默认值对 select 元素为 true。boolean useCache context.getBooleanAttribute(useCache, isSelect);//根嵌套结果相关boolean resultOrdered context.getBooleanAttribute(resultOrdered, false);//引入SQL片段// Include Fragments before parsingXMLIncludeTransformer includeParser new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.//处理selectKeyprocessSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: selectKey and include were parsed and removed)SqlSource sqlSource langDriver.createSqlSource(configuration, context, parameterTypeClass);//String resultSets context.getStringAttribute(resultSets);String keyProperty context.getStringAttribute(keyProperty);String keyColumn context.getStringAttribute(keyColumn);//设置主键自增的方式KeyGenerator keyGenerator;String keyStatementId id SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator configuration.getKeyGenerator(keyStatementId);} else {keyGenerator context.getBooleanAttribute(useGeneratedKeys,configuration.isUseGeneratedKeys() SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}//通过buildAssistant将解析得到的参数设置构造成MappedStatement对象builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);} 将解析得到参数通过BuilderAssistant.addMappedStatement方法解析得到MappedStatement对象。 上面已经说过sqlsource表示的一个SQL语句因此我们关注langDriver.createSqlSource这个方法。看XMLLanguageDriver这个实现。 Overridepublic SqlSource createSqlSource(Configuration configuration, XNode script, Class? parameterType) {XMLScriptBuilder builder new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();} 可以看到他将创建sqlsource的工作交给了XMLScrpitBuilder又一个创建者模式的应用。来看parseScriptNode方法 public SqlSource parseScriptNode() {//解析SQL语句节点创建MixedSqlNode对象MixedSqlNode rootSqlNode parseDynamicTags(context);SqlSource sqlSource null;//根据是否是动态的语句创建DynamicSqlSource或是RawSqlSource对象并返回if (isDynamic) {sqlSource new DynamicSqlSource(configuration, rootSqlNode);} else {sqlSource new RawSqlSource(configuration, rootSqlNode, parameterType);}return sqlSource;} MixedSqlNode是SqlNode的一个实现包含了各个子节点用来遍历输出子节点。SqlNode还有很多不同的实现分别对应不同的节点类型。对应关系如下 SqlNode实现对应SQL语句中的类型TextSqlNode${}IfSqlNodeIf节点TrimSqlNode/WhereSqlNode/SetSqlNodeTrim/Where/Set节点Foreach节点foreach标签ChooseSqlNode节点choose/when/otherwhise节点ValDeclSqlNode节点bind节点StaticTextSqlNode不含上述节点 除了StaticTextSqlNode节点外其余对应的都是动态语句。 因此我们本文的关注点在StaticTextSqlNode。 让我们对应文初sql语句的解析来看一下parseDynamicTags方法为了便于理解我将在右边注释出每一步的结果 protected MixedSqlNode parseDynamicTags(XNode node) {// node是我们要解析的SQL语句: select resultTypeorg.apache.ibatis.domain.blog.Author idselectAllAuthorsselect * from author/selectListSqlNode contents new ArrayListSqlNode();//获取SQL下面的子节点NodeList children node.getNode().getChildNodes();//这里的children只有一个节点//遍历子节点解析成对应的sqlNode类型并添加到contents中for (int i 0; i children.getLength(); i) {XNode child node.newXNode(children.item(i));//第一个child节点就是SQL中的文本数据select * from author//如果是文本节点则先解析成TextSqlNode对象if (child.getNode().getNodeType() Node.CDATA_SECTION_NODE || child.getNode().getNodeType() Node.TEXT_NODE) {//获取文本信息String data child.getStringBody();//data:select * from author//创建TextSqlNode对象TextSqlNode textSqlNode new TextSqlNode(data);//判断是否是动态Sql其过程会调用GenericTokenParser判断文本中是否含有${字符if (textSqlNode.isDynamic()) {//如果是动态SQL,则直接使用TextSqlNode类型并将isDynamic标识置为truecontents.add(textSqlNode);isDynamic true;} else {//不是动态sql则创建StaticTextSqlNode对象表示静态SQLcontents.add(new StaticTextSqlNode(data));}} else if (child.getNode().getNodeType() Node.ELEMENT_NODE) { //其他类型的节点由不同的节点处理器来对应处理成本成不同的SqlNode类型String nodeName child.getNode().getNodeName();NodeHandler handler nodeHandlerMap.get(nodeName);if (handler null) {throw new BuilderException(Unknown element nodeName in SQL statement.);}handler.handleNode(child, contents);isDynamic true;}}//用contents构建MixedSqlNode对象return new MixedSqlNode(contents);} 上述过程中我们主要关注静态SQL的解析过程对于动态SQL的解析将在之后介绍。 得到MixedSqlNode后静态的SQL会创建出RawSqlSource对象。 看一下RawSqlSource public class RawSqlSource implements SqlSource {//内部封装的sqlSource对象getBoundSql方法会委托给这个对象private final SqlSource sqlSource;public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class? parameterType) {this(configuration, getSql(configuration, rootSqlNode), parameterType);}public RawSqlSource(Configuration configuration, String sql, Class? parameterType) {//创建sqlSourceBuilderSqlSourceBuilder sqlSourceParser new SqlSourceBuilder(configuration);Class? clazz parameterType null ? Object.class : parameterType;//解析sql创建StaticSqlSource对象sqlSource sqlSourceParser.parse(sql, clazz, new HashMapString, Object());}//获取sql语句private static String getSql(Configuration configuration, SqlNode rootSqlNode) {DynamicContext context new DynamicContext(configuration, null);//这里的rootSqlNode就是之前得到的MixedSqlNode它会遍历内部的SqlNode,逐个调用sqlNode的apply方法。StaticTextSqlNode会直接context.appendSql方法rootSqlNode.apply(context);return context.getSql();}Overridepublic BoundSql getBoundSql(Object parameterObject) {return sqlSource.getBoundSql(parameterObject);}} 代码相对简单主要的步骤就是1通过SqlNode获得原始SQL语句2创建SqlSourceBuilder对象解析SQL语句并创建StaticSqlSource对象3将getBoundSql方法委托给内部的staticSqlSource对象。 其中比较关键的一步是解析原始SQL语句并创建StaticSqlSource对象。因此我们继续看SqlSourceBuilder对象。 public class SqlSourceBuilder extends BaseBuilder {private static final String parameterProperties javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName;public SqlSourceBuilder(Configuration configuration) {super(configuration);}public SqlSource parse(String originalSql, Class? parameterType, MapString, Object additionalParameters) {//创建TokenHandler用来将原始Sql中的#{} 解析成?ParameterMappingTokenHandler handler new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);GenericTokenParser parser new GenericTokenParser(#{, }, handler);//解析原始sqlString sql parser.parse(originalSql);//创建出StaticSqlSource对象return new StaticSqlSource(configuration, sql, handler.getParameterMappings());}private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {private ListParameterMapping parameterMappings new ArrayListParameterMapping();private Class? parameterType;private MetaObject metaParameters;public ParameterMappingTokenHandler(Configuration configuration, Class? parameterType, MapString, Object additionalParameters) {super(configuration);this.parameterType parameterType;this.metaParameters configuration.newMetaObject(additionalParameters);}public ListParameterMapping getParameterMappings() {return parameterMappings;}Overridepublic String handleToken(String content) {//解析#{}中的参数创建ParameterMapping对象parameterMappings.add(buildParameterMapping(content));//将#{}替换成?return ?;}private ParameterMapping buildParameterMapping(String content) {MapString, String propertiesMap parseParameterMapping(content);String property propertiesMap.get(property);Class? propertyType;if (metaParameters.hasGetter(property)) { // issue #448 get type from additional paramspropertyType metaParameters.getGetterType(property);} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {propertyType parameterType;} else if (JdbcType.CURSOR.name().equals(propertiesMap.get(jdbcType))) {propertyType java.sql.ResultSet.class;} else if (property null || Map.class.isAssignableFrom(parameterType)) {propertyType Object.class;} else {MetaClass metaClass MetaClass.forClass(parameterType, configuration.getReflectorFactory());if (metaClass.hasGetter(property)) {propertyType metaClass.getGetterType(property);} else {propertyType Object.class;}}ParameterMapping.Builder builder new ParameterMapping.Builder(configuration, property, propertyType);Class? javaType propertyType;String typeHandlerAlias null;for (Map.EntryString, String entry : propertiesMap.entrySet()) {String name entry.getKey();String value entry.getValue();if (javaType.equals(name)) {javaType resolveClass(value);builder.javaType(javaType);} else if (jdbcType.equals(name)) {builder.jdbcType(resolveJdbcType(value));} else if (mode.equals(name)) {builder.mode(resolveParameterMode(value));} else if (numericScale.equals(name)) {builder.numericScale(Integer.valueOf(value));} else if (resultMap.equals(name)) {builder.resultMapId(value);} else if (typeHandler.equals(name)) {typeHandlerAlias value;} else if (jdbcTypeName.equals(name)) {builder.jdbcTypeName(value);} else if (property.equals(name)) {// Do Nothing} else if (expression.equals(name)) {throw new BuilderException(Expression based parameters are not supported yet);} else {throw new BuilderException(An invalid property name was found in mapping #{ content }. Valid properties are parameterProperties);}}if (typeHandlerAlias ! null) {builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));}return builder.build();}private MapString, String parseParameterMapping(String content) {try {return new ParameterExpression(content);} catch (BuilderException ex) {throw ex;} catch (Exception ex) {throw new BuilderException(Parsing error was found in mapping #{ content }. Check syntax #{property|(expression), var1value1, var2value2, ...} , ex);}}}} parse方法主要分为以下几步 1创建了ParameterMappingTokenHandler对象 2将ParameterMappingTokenHandler对象传入GenericTokenParser的构造函数中创建GenericTokenParser对象 3通过GenericTokenParser对象解析原始SQL这个过程中会将#{}替换成?并将#{}中的参数解析形成ParamterMapping对象 4用得到的SQL和ParamterMapping对象创建StaticSqlSource对象。 解析完成后回到一开始的XMLMapperBuilder它会在资源添加到已加载的列表中并bindMapperForNamespace方法中为创建的MappedStatement添加命名空间。转载于:https://www.cnblogs.com/insaneXs/p/9083003.html