MyBatis原理--配置解析
2015-06-25 16:16
316 查看
MyBatis解析配置文件的入口在:
继续看XMLConfigBuilder.java
关于MyBatis配置相关的一些解释可以参考这里,看初始化配置这部分代码的时候,最好心里对各种配置有个概念。
下面主要看一下对于mappers的解析:
看一下XMLMapperBuilder的内容:
XMLStatementBuilder具体内容:
SqlSource的生成过程这里看一下XMLLanguageDriver怎么做的:
解析的主要逻辑就是上面这些,继续看一下其他的:
还是XMLMapperBuilder.parse方法
解析完毕!
配置解析其实没那么复杂,主要是需要对配置文件的格式有概念,具体代码不要担心第一遍看完不知其所以然,多看两遍就OK了!
public class SqlSessionFactoryBuilder { //一般单独使用MyBatis的话配置文件解析的入口是这个方法,reader是对配置文件的封装,一般environment和properties都是null public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //parse.parse()返回的是一个Configuration对象,Configuration对象中包含了所有的配置信息 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } //如果使用spring + mybatis,SqlSessionFactoryBean会调用这个方法完成配置文件的初始化。 public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } }
继续看XMLConfigBuilder.java
public class XMLConfigBuilder entends BaseBuilder { public XMLConfigBuilder(Reader reader, String environment, Properties props) { //XPathParser主要是解析XML文档,将其转化为一个Document对象 this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } //这边可以看到parse方法的本体了, public Configuration parse() { if (parsed) { throw new BuilderException("Each MapperConfigParser can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } //解析configuration节点,下面这个方法根据名字基本上都能判定是做什么事情的了。(论名字的重要性) private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } }
关于MyBatis配置相关的一些解释可以参考这里,看初始化配置这部分代码的时候,最好心里对各种配置有个概念。
下面主要看一下对于mappers的解析:
public class XMLConfigBuilder extends BaseBuilder { private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); //将包下面所有的父类是Object的类(除去接口,虚拟类),都添加到configuration.mappers中。 configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); //resource,url,class三个属性必定有一个要存在,resource或者url都是指明mapper文件的位置,获取到文件之后交给XMLMapperBuilder处理。 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { //将mapperClass对应的class对象添加到configuration.mappers中。 Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } } }
看一下XMLMapperBuilder的内容:
public class XMLMapperBuilder extends BaseBuilder { public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); } private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { super(configuration); this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; } public void parse() { //首先会判断一下是否以及load过该resource if (!configuration.isResourceLoaded(resource)) { //主要逻辑在这个里面 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } //如果已经解析过,看是否还有解析失败的 parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); builderAssistant.setCurrentNamespace(namespace); //可以看到,这边分别解析不同节点 //cache-ref,cache,parameterMap(官方表示已过时),resultMap这几个基本上都是获取到各个参数,然后将其赋值给对象的响应属性。 //最后会lock一下对象中的collection,将其变为不可修改的。然后再将其放入configuration。 //sql的解析也差不多是这样,不过sql的解析结果最后会让如sqlFragments中。 cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); //主要来看一下对于语句的处理 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e); } } private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { //如果解析过程抛出异常,将其添加到configuration.inCompleteStatement中,等着后面再去处理。 configuration.addIncompleteStatement(statementParser); } } } }
XMLStatementBuilder具体内容:
public class XMLStatementBuilder extends BaseBuilder { //构造方法也只是赋值而已 public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) { super(configuration); this.builderAssistant = builderAssistant; this.context = context; this.requiredDatabaseId = databaseId; } public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } //获取各种参数,并将部分参数转化为具体的Java对象 Integer fetchSize = context.getIntAttribute("fetchSize", null); Integer timeout = context.getIntAttribute("timeout", null); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing //如果包含了inculde,用原始sql语句进行替换 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes, // in case if IncompleteElementException (issue #291) //解析selectKey,根据代码来看,如果你在同一个语句中声明了多个selectKey,那么只有最后一个会生效。 //selectKey的解析过程跟具体sql语句的解析过程比较相像,selectKey最后也会变为一个MappedStatement对象 List<XNode> selectKeyNodes = context.evalNodes("selectKey"); if (configuration.getDatabaseId() != null) { parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId()); } parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) //selectKey和include解析之后已经被移除了,具体逻辑后面有 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); //设置id生成器 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)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } //这里就是给MappedStatement设置各种属性了,其中resultMap,parameterMap会根据名字,找到对应的对象,如果名字为空那么会根据resultType, parameterType来处理 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver); //至此,一个statement节点的解析就完成了 } //解析selectKey的过程 public void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) { for (XNode nodeToHandle : list) { //这个地方可以看出来,解析过程中所有selectKey节点用的id都是一样的(父节点的id+一个常量) String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX; String databaseId = nodeToHandle.getStringAttribute("databaseId"); if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) { parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId); } } } public void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) { String resultType = nodeToHandle.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString())); String keyProperty = nodeToHandle.getStringAttribute("keyProperty"); boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER")); //defaults boolean useCache = false; boolean resultOrdered = false; KeyGenerator keyGenerator = new NoKeyGenerator(); Integer fetchSize = null; Integer timeout = null; boolean flushCache = false; String parameterMap = null; String resultMap = null; ResultSetType resultSetTypeEnum = null; //这里返回的是DynamicSqlSource对象,sql语句在这里被转化为了SqlSource对象 SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass); SqlCommandType sqlCommandType = SqlCommandType.SELECT; //这里就是给MappedStatement设置各种属性了,其中resultMap,parameterMap会根据名字,找到对应的对象,如果名字为空那么会根据resultType, parameterType来处理 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, null, databaseId, langDriver); id = builderAssistant.applyCurrentNamespace(id, false); MappedStatement keyStatement = configuration.getMappedStatement(id, false); //添加key生成器 configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore)); nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode()); } //到这里selectKey的解析完成了,可以回头继续看了。 }
SqlSource的生成过程这里看一下XMLLanguageDriver怎么做的:
public class XMLLanguageDriver implements LanguageDriver { public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script); return builder.parseScriptNode(); } } public class XMLScriptBuilder extends BaseBuilder { private XNode context; public XMLScriptBuilder(Configuration configuration, XNode context) { super(configuration); this.context = context; } public SqlSource parseScriptNode() { //主要解析在这个方法里面 List<SqlNode> contents = parseDynamicTags(context); //简单的封装 MixedSqlNode rootSqlNode = new MixedSqlNode(contents); SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode); return sqlSource; } //该方法里面将sql语句封装成了SqlNode,像动态语句支持的<if>,<where>等,不同的节点被封装成了不同的SqlNode,所以返回会是一个list private List<SqlNode> parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<SqlNode>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); String nodeName = child.getNode().getNodeName(); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { //如果是内容节点或者CDATA节点 String data = child.getStringBody(""); contents.add(new TextSqlNode(data)); } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE && !"selectKey".equals(nodeName)) { // issue #628 //如果是元素节点(<if>,<where>这样就作为一个元素节点) //这里根据不同的节点名称返回不同的handler对象,handler对象有WhereHandler, SetHandler, IfHandler, ForEachHandler等 NodeHandler handler = nodeHandlers.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); } } return contents; } }
解析的主要逻辑就是上面这些,继续看一下其他的:
还是XMLMapperBuilder.parse方法
public void parse() { if (!configuration.isResourceLoaded(resource)) { //上面的都是这个方法的内容 configurationElement(parser.evalNode("/mapper")); // 将resource添加到已解析的列表中 configuration.addLoadedResource(resource); //将该resource的namespace对应的class添加到已解析列表中(如果对应class不存在,则忽略) bindMapperForNamespace(); } //重新解析之前由于异常而暂时没有处理的resultMap,cache,statement。 parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); }
解析完毕!
配置解析其实没那么复杂,主要是需要对配置文件的格式有概念,具体代码不要担心第一遍看完不知其所以然,多看两遍就OK了!
相关文章推荐
- 黑马程序员——Java基础之面向对象(二)文档注释与设计模式
- 回答自己的提问
- CAS实现SSO 学习(一)
- href=#与href=javascriptvoid(0)的区别
- java中File文件的创建,删除,复制,移动,以及目录文件的删除等等,主要是文件及文件夹的操作
- MongoDB源码概述——内存管理和存储引擎
- SQL Server下实现利用SQL Server Agent Job对索引重建实现Balance Load
- BZGFormField-文本输入框检查
- Java书籍推荐
- MySQL 获得当前日期时间 函数
- 在HDFS集群中优化secondary namenode到datanode1节点上,并做重启hdfs集群后,datanode1启动失败
- 计时器运行半小时后卡卡的问题
- Maven环境快速搭建
- HDU 1692 Destroy the Well of Life-卡时间-(枚举+剪枝)
- Effective C++ 条款18
- android:descendantFocusability用法简析
- 安卓通过spinner实现二级选择地区(根据思路可以写三级)
- NSURLConnection Get
- Cisco 12系列 AP 初始化配置-2-初始化配置
- Apache log4net™ 手册——介绍【翻译】