Ibatis源码学习(三):配置文件的解析
ibatis文件的文件的解析从主文件sql-map-config.xml开始。见类:com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser
public SqlMapConfigParser() { parser.setValidation(true); parser.setEntityResolver(new SqlMapClasspathEntityResolver()); addSqlMapConfigNodelets(); //"/sqlMapConfig/end()" addGlobalPropNodelets(); //"/sqlMapConfig/properties" addSettingsNodelets(); //"/sqlMapConfig/settings" addTypeAliasNodelets(); //"/sqlMapConfig/typeAlias" addTypeHandlerNodelets(); //"/sqlMapConfig/typeHandler" addTransactionManagerNodelets(); //"/sqlMapConfig/transactionManager/property" ... addSqlMapNodelets(); //"/sqlMapConfig/sqlMap" addResultObjectFactoryNodelets(); //"/sqlMapConfig/resultObjectFactory" ... }
先将上诉的节点默认添加进来,然后根据具体的设置在利用回调来初始化各个节点的具体属性值,以addSettingsNodelets()为例:
private void addSettingsNodelets() { parser.addNodelet("/sqlMapConfig/settings", new Nodelet() { public void process(Node node) throws Exception { //该方法做会调用 Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps()); SqlMapConfiguration config = state.getConfig(); String classInfoCacheEnabledAttr = attributes.getProperty("classInfoCacheEnabled"); boolean classInfoCacheEnabled = (classInfoCacheEnabledAttr == null || "true".equals(classInfoCacheEnabledAttr)); config.setClassInfoCacheEnabled(classInfoCacheEnabled); String lazyLoadingEnabledAttr = attributes.getProperty("lazyLoadingEnabled"); boolean lazyLoadingEnabled = (lazyLoadingEnabledAttr == null || "true".equals(lazyLoadingEnabledAttr)); config.setLazyLoadingEnabled(lazyLoadingEnabled); String statementCachingEnabledAttr = attributes.getProperty("statementCachingEnabled"); boolean statementCachingEnabled = (statementCachingEnabledAttr == null || "true".equals(statementCachingEnabledAttr)); config.setStatementCachingEnabled(statementCachingEnabled); String cacheModelsEnabledAttr = attributes.getProperty("cacheModelsEnabled"); boolean cacheModelsEnabled = (cacheModelsEnabledAttr == null || "true".equals(cacheModelsEnabledAttr)); config.setCacheModelsEnabled(cacheModelsEnabled); String enhancementEnabledAttr = attributes.getProperty("enhancementEnabled"); boolean enhancementEnabled = (enhancementEnabledAttr == null || "true".equals(enhancementEnabledAttr)); config.setEnhancementEnabled(enhancementEnabled); String useColumnLabelAttr = attributes.getProperty("useColumnLabel"); boolean useColumnLabel = (useColumnLabelAttr == null || "true".equals(useColumnLabelAttr)); config.setUseColumnLabel(useColumnLabel); String forceMultipleResultSetSupportAttr = attributes.getProperty("forceMultipleResultSetSupport"); boolean forceMultipleResultSetSupport = "true".equals(forceMultipleResultSetSupportAttr); config.setForceMultipleResultSetSupport(forceMultipleResultSetSupport); String defaultTimeoutAttr = attributes.getProperty("defaultStatementTimeout"); Integer defaultTimeout = defaultTimeoutAttr == null ? null : Integer.valueOf(defaultTimeoutAttr); config.setDefaultStatementTimeout(defaultTimeout); String useStatementNamespacesAttr = attributes.getProperty("useStatementNamespaces"); boolean useStatementNamespaces = "true".equals(useStatementNamespacesAttr); state.setUseStatementNamespaces(useStatementNamespaces); } }); }
parser.addNodelet(xpath,nodelet)添加一个节点,见类:com.ibatis.common.xml.NodeletParser
public void addNodelet(String xpath, Nodelet nodelet) { letMap.put(xpath, nodelet); }
在这个类中,解析节点的时候,
public void parse(Reader reader) throws NodeletException { try { Document doc = createDocument(reader); parse(doc.getLastChild()); } catch (Exception e) { throw new NodeletException("Error parsing XML. Cause: " + e, e); } } public void parse(InputStream inputStream) throws NodeletException { try { Document doc = createDocument(inputStream); parse(doc.getLastChild()); } catch (Exception e) { throw new NodeletException("Error parsing XML. Cause: " + e, e); } } /** * Begins parsing from the provided Node. */ public void parse(Node node) { Path path = new Path(); processNodelet(node, "/"); process(node, path); } /** * A recursive method that walkes the DOM tree, registers XPaths and * calls Nodelets registered under those XPaths. */ private void process(Node node, Path path) { if (node instanceof Element) { // Element String elementName = node.getNodeName(); path.add(elementName); processNodelet(node, path.toString()); processNodelet(node, new StringBuffer("//").append(elementName).toString()); // Attribute NamedNodeMap attributes = node.getAttributes(); int n = attributes.getLength(); for (int i = 0; i < n; i++) { Node att = attributes.item(i); String attrName = att.getNodeName(); path.add("@" + attrName); processNodelet(att, path.toString()); processNodelet(node, new StringBuffer("//@").append(attrName).toString()); path.remove(); } // Children NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { process(children.item(i), path); } path.add("end()"); processNodelet(node, path.toString()); path.remove(); path.remove(); } else if (node instanceof Text) { // Text path.add("text()"); processNodelet(node, path.toString()); processNodelet(node, "//text()"); path.remove(); } } private void processNodelet(Node node, String pathString) { Nodelet nodelet = (Nodelet) letMap.get(pathString); if (nodelet != null) { try { nodelet.process(node); //这个方法进行回调,设置初始值 } catch (Exception e) { throw new RuntimeException("Error parsing XPath '" + pathString + "'. Cause: " + e, e); } } }
在com.ibatis.sqlmap.engine.builder.xml.SqlMapParser里面解析到statement时:
protected void addStatementNodelets() { parser.addNodelet("/sqlMap/statement", new Nodelet() { public void process(Node node) throws Exception { statementParser.parseGeneralStatement(node, new MappedStatement()); } }); parser.addNodelet("/sqlMap/insert", new Nodelet() { public void process(Node node) throws Exception { statementParser.parseGeneralStatement(node, new InsertStatement()); } }); parser.addNodelet("/sqlMap/update", new Nodelet() { public void process(Node node) throws Exception { statementParser.parseGeneralStatement(node, new UpdateStatement()); } }); parser.addNodelet("/sqlMap/delete", new Nodelet() { public void process(Node node) throws Exception { statementParser.parseGeneralStatement(node, new DeleteStatement()); } }); parser.addNodelet("/sqlMap/select", new Nodelet() { public void process(Node node) throws Exception { statementParser.parseGeneralStatement(node, new SelectStatement()); } }); parser.addNodelet("/sqlMap/procedure", new Nodelet() { public void process(Node node) throws Exception { statementParser.parseGeneralStatement(node, new ProcedureStatement()); } }); }实际上调用了com.ibatis.sqlmap.engine.builder.xml.SqlStatementParser的parseGeneralStatement方法:
public void parseGeneralStatement(Node node, MappedStatement statement) { // ... MappedStatementConfig statementConf = state.getConfig().newMappedStatementConfig(id, statement, new XMLSqlSource(state, node), parameterMapName, parameterClass, resultMapName, additionalResultMapNames, resultClass, additionalResultClasses, resultSetType, fetchSizeInt, allowRemappingBool, timeoutInt, cacheModelName, xmlResultName); findAndParseSelectKey(node, statementConf); }此时又是调用了com.ibatis.sqlmap.engine.config.SqlMapConfiguration的newMappedStatementConfig方法:
public MappedStatementConfig newMappedStatementConfig(String id, MappedStatement statement, SqlSource processor, String parameterMapName, Class parameterClass, String resultMapName, String[] additionalResultMapNames, Class resultClass, Class[] additionalResultClasses, String resultSetType, Integer fetchSize, boolean allowRemapping, Integer timeout, String cacheModelName, String xmlResultName) { return new MappedStatementConfig(this, id, statement, processor, parameterMapName, parameterClass, resultMapName, additionalResultMapNames, resultClass, additionalResultClasses, cacheModelName, resultSetType, fetchSize, allowRemapping, timeout, defaultStatementTimeout, xmlResultName); }返回一个com.ibatis.sqlmap.engine.config.MappedStatementConfig的实例
MappedStatementConfig(SqlMapConfiguration config, String id, MappedStatement statement, SqlSource processor, String parameterMapName, Class parameterClass, String resultMapName, String[] additionalResultMapNames, Class resultClass, Class[] additionalResultClasses, String cacheModelName, String resultSetType, Integer fetchSize, boolean allowRemapping, Integer timeout, Integer defaultStatementTimeout, String xmlResultName) { this.errorContext = config.getErrorContext(); this.client = config.getClient(); SqlMapExecutorDelegate delegate = client.getDelegate(); this.typeHandlerFactory = config.getTypeHandlerFactory(); errorContext.setActivity("parsing a mapped statement"); errorContext.setObjectId(id + " statement"); errorContext.setMoreInfo("Check the result map name."); if (resultMapName != null) { statement.setResultMap(client.getDelegate().getResultMap(resultMapName)); if (additionalResultMapNames != null) { for (int i = 0; i < additionalResultMapNames.length; i++) { statement.addResultMap(client.getDelegate().getResultMap(additionalResultMapNames[i])); } } } errorContext.setMoreInfo("Check the parameter map name."); if (parameterMapName != null) { statement.setParameterMap(client.getDelegate().getParameterMap(parameterMapName)); } statement.setId(id); statement.setResource(errorContext.getResource()); if (resultSetType != null) { if ("FORWARD_ONLY".equals(resultSetType)) { statement.setResultSetType(new Integer(ResultSet.TYPE_FORWARD_ONLY)); } else if ("SCROLL_INSENSITIVE".equals(resultSetType)) { statement.setResultSetType(new Integer(ResultSet.TYPE_SCROLL_INSENSITIVE)); } else if ("SCROLL_SENSITIVE".equals(resultSetType)) { statement.setResultSetType(new Integer(ResultSet.TYPE_SCROLL_SENSITIVE)); } } if (fetchSize != null) { statement.setFetchSize(fetchSize); } // set parameter class either from attribute or from map (make sure to match) ParameterMap parameterMap = statement.getParameterMap(); if (parameterMap == null) { statement.setParameterClass(parameterClass); } else { statement.setParameterClass(parameterMap.getParameterClass()); } // process SQL statement, including inline parameter maps errorContext.setMoreInfo("Check the SQL statement."); Sql sql = processor.getSql(); setSqlForStatement(statement, sql); // set up either null result map or automatic result mapping ResultMap resultMap = (ResultMap) statement.getResultMap(); if (resultMap == null && resultClass == null) { statement.setResultMap(null); } else if (resultMap == null) { resultMap = buildAutoResultMap(allowRemapping, statement, resultClass, xmlResultName); statement.setResultMap(resultMap); if (additionalResultClasses != null) { for (int i = 0; i < additionalResultClasses.length; i++) { statement.addResultMap(buildAutoResultMap(allowRemapping, statement, additionalResultClasses[i], xmlResultName)); } } } statement.setTimeout(defaultStatementTimeout); if (timeout != null) { try { statement.setTimeout(timeout); } catch (NumberFormatException e) { throw new SqlMapException("Specified timeout value for statement " + statement.getId() + " is not a valid integer"); } } errorContext.setMoreInfo(null); errorContext.setObjectId(null); statement.setSqlMapClient(client); if (cacheModelName != null && cacheModelName.length() > 0 && client.getDelegate().isCacheModelsEnabled()) {//这个判断很关键 CacheModel cacheModel = client.getDelegate().getCacheModel(cacheModelName); mappedStatement = new CachingStatement(statement, cacheModel); } else { mappedStatement = statement; } rootStatement = statement; delegate.addMappedStatement(mappedStatement); }
上诉的if (cacheModelName != null && cacheModelName.length() > 0 && client.getDelegate().isCacheModelsEnabled())判断,直接决定了是否启用缓存,假设前面没有设置setting这个属性,该值是为空的,不存在默认值,故此时是不会启用缓存的。
总结:
当解析实际的配置文件sql-map-config.xml时,
如果设置了<setting>这个属性,则回调addSettingsNodelets里面的addNodelet里面的Nodelet的process方法。
设置里面的属性值,假如此时没有设置<setting>属性(注意,是没有设置,不是设置为空,因为设置为空,同样的调用回调,设置了默认值),则不再调用此回调函数,则此时将没有默认值,如果此时想启动缓存等,都是无效的。
故,此时应注意,如果想利用默认值,setting是必须设置的,哪怕设置为空。但,如果设置了setting后,文件的先后顺序是有要求的,如果A文件利用到b文件的命名空间的某个引用id,则此时必须使得B文件在A文件之前装载,否则将出现文件A引用文件B中的id时出现不存在的异常。