深入理解MyBatis(二)—MyBatis初始化
2017-09-05 20:09
351 查看
深入理解MyBatis(二)—MyBatis初始化
MyBatis的配置信息存储在XML配置文件中,使用Configuration对象作为一个所偶有配置信息的容器,Configuration对象的组织结构和XML配置文件的组织结构几乎完全一样,因此MyBatis初始化就是加载XML配置文件,创建Configuration对象的过程;个人主页:tuzhenyu’s page
原文地址:深入理解MyBatis(二)—MyBatis初始化
(1) Configuration创建和加载
MyBatis的初始化入口是从SqlSeesionFactory的创建开始的,将生成的配置文件输入流传递到SqlSessionBuilder类的build()方法中构建SqlSessionFactory实例String resource = "mybatis/config.xml"; InputStream is = Main.class.getClassLoader().getResourceAsStream(resource); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
调用build()方法的重载方法,首先根据配置文件输入流进行XML解析,再根据解析结果创建SqlSessionFactory实例
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { SqlSessionFactory var5; try { XMLConfigBuilder e = new XMLConfigBuilder(reader, environment, properties); var5 = this.build(e.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException var13) { ; } } return var5; }
XMLConfigBuilder在构造器中创建XPathParser实例,进行XML配置输入流的解析
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); }
commentConstrutor()方法主要是对validation等属性进行定义,根据输入流
4000
创建Document的主要逻辑主要在createDocoument()方法中;
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) { this.commonConstructor(validation, variables, entityResolver); this.document = this.createDocument(new InputSource(inputStream)); }
createDocument()方法根据XML输入流创建Document实例的方法和Spring中将XML文件解析成Doucment方法相同,都是通过SAX的方法解析XML文档;
首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource()返回Document对象;在解析过程中调用”org.xml.sax”包下额SAX解析方法,创建DOM树;
解完的Document会存储在XPathParser实例对象中,XPathParser对象会存储在XMLConfigBuilder对象中,调用XMLConfigBuilder的parse()方法进行进一步解析;
private Document createDocument(InputSource inputSource) { try { DocumentBuilderFactory e = DocumentBuilderFactory.newInstance(); e.setValidating(this.validation); e.setNamespaceAware(false); e.setIgnoringComments(true); e.setIgnoringElementContentWhitespace(false); e.setCoalescing(false); e.setExpandEntityReferences(true); DocumentBuilder builder = e.newDocumentBuilder(); builder.setEntityResolver(this.entityResolver); builder.setErrorHandler(new ErrorHandler() { public void error(SAXParseException exception) throws SAXException { throw exception; } public void fatalError(SAXParseException exception) throws SAXException { throw exception; } public void warning(SAXParseException exception) throws SAXException { } }); return builder.parse(inputSource); } catch (Exception var4) { throw new BuilderException("Error creating document instance. Cause: " + var4, var4); } }
XPathParser实例的parse()方法通过xpath的方法对XML进行解析,首先定位configuration标签作为根标签;
与Spring解析XML不同的是Spring仅仅通过SAX方法将XML文件解析成Document对象,然后对根节点下的标签进行遍历解析生成对应的BeanDefinitiion,而MyBatis在解析玩完Document后利用XPath对标签进行定位获取;
public Configuration parse() { if(this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }
调用parseConfiguration()方法对XML进行解析,并根据标签解析结果对configuration对象进行属性的初始化设置
private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); Properties e = this.settingsAsProperties(root.evalNode("settings")); this.loadCustomVfs(e); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(e); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
创建Configuration实例对象装载在DefaultSqlSessionFactory对象中,并返回DefaultSqlSessionFactory对象实例用于创建SqlSession;
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
(2) Mappers标签的解析
mappers标签的解析主要是遍历mappers标签下的所有mapper标签,将mapper映射文件内的每条SQL语句对象化成一个个MapperStatement对象;mapper标签解析的入口是mapperElement()方法;private void mapperElement(XNode parent) throws Exception { if(parent != null) { Iterator var2 = parent.getChildren().iterator(); while(true) { while(var2.hasNext()) { XNode child = (XNode)var2.next(); String resource; if("package".equals(child.getName())) { resource = child.getStringAttribute("name"); this.configuration.addMappers(resource); } else { resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); XMLMapperBuilder mapperParser; InputStream mapperInterface1; if(resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); mapperInterface1 = Resources.getResourceAsStream(resource); mapperParser = new XMLMapperBuilder(mapperInterface1, this.configuration, resource, this.configuration.getSqlFragments()); mapperParser.parse(); } else if(resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); mapperInterface1 = Resources.getUrlAsStream(url); mapperParser = new XMLMapperBuilder(mapperInterface1, this.configuration, url, this.configuration.getSqlFragments()); mapperParser.parse(); } else { if(resource != null || url != null || mapperClass == null) { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } Class mapperInterface = Resources.classForName(mapperClass); this.configuration.addMapper(mapperInterface); } } } return; } } }
每一个mapper映射文件都会创建一个XMLMapperBuilder对象,调用其parse()方法进行单个映射文件mapper的解析;
主要是对mapper映射文件下的ResultMap,cache-ref和具体的sql操作语句(select,insert,update,delete)进行解析;
public void parse() { if(!this.configuration.isResourceLoaded(this.resource)) { this.configurationElement(this.parser.evalNode("/mapper")); this.configuration.addLoadedResource(this.resource); this.bindMapperForNamespace(); } this.parsePendingResultMaps(); this.parsePendingCacheRefs(); this.parsePendingStatements(); }
对具体sql操作语句的解析通过调用buildStatementFromContext()方法实现;
private void configurationElement(XNode context) { try { String e = context.getStringAttribute("namespace"); if(e != null && !e.equals("")) { this.builderAssistant.setCurrentNamespace(e); this.cacheRefElement(context.evalNode("cache-ref")); this.cacheElement(context.evalNode("cache")); this.parameterMapElement(context.evalNodes("/mapper/parameterMap")); this.resultMapElements(context.evalNodes("/mapper/resultMap")); this.sqlElement(context.evalNodes("/mapper/sql")); this.buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } else { throw new BuilderException("Mapper\'s namespace cannot be empty"); } } catch (Exception var3) { throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3); } }
通过XPath从mapper映射文件中获取全部SQL操作集合,每条操作创建一个XMLStatementBuilder对象进行解析
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { Iterator var3 = list.iterator(); while(var3.hasNext()) { XNode context = (XNode)var3.next(); XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId); try { statementParser.parse d380 StatementNode(); } catch (IncompleteElementException var7) { this.configuration.addIncompleteStatement(statementParser); } } }
在XMLStatementBuilder对象的parseStatementNode()方法中构建SqlSource对象和MappedStatement对象
public void parseStatementNode() { String id = this.context.getStringAttribute("id"); String databaseId = this.context.getStringAttribute("databaseId"); if(this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { Integer fetchSize = this.context.getIntAttribute("fetchSize"); Integer timeout = this.context.getIntAttribute("timeout"); String parameterMap = this.context.getStringAttribute("parameterMap"); String parameterType = this.context.getStringAttribute("parameterType"); Class parameterTypeClass = this.resolveClass(parameterType); String resultMap = this.context.getStringAttribute("resultMap"); String resultType = this.context.getStringAttribute("resultType"); String lang = this.context.getStringAttribute("lang"); LanguageDriver langDriver = this.getLanguageDriver(lang); Class resultTypeClass = this.resolveClass(resultType); String resultSetType = this.context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType); String nodeName = this.context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = this.context.getBooleanAttribute("flushCache", Boolean.valueOf(!isSelect)).booleanValue(); boolean useCache = this.context.getBooleanAttribute("useCache", Boolean.valueOf(isSelect)).booleanValue(); boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", Boolean.valueOf(false)).booleanValue(); XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant); includeParser.applyIncludes(this.context.getNode()); this.processSelectKeyNodes(id, parameterTypeClass, langDriver); SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass); String resultSets = this.context.getStringAttribute("resultSets"); String keyProperty = this.context.getStringAttribute("keyProperty"); String keyColumn = this.context.getStringAttribute("keyColumn"); String keyStatementId = id + "!selectKey"; keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true); Object keyGenerator; if(this.configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = this.configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", Boolean.valueOf(this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))).booleanValue()?Jdbc3KeyGenerator.INSTANCE:NoKeyGenerator.INSTANCE; } this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } }
在创建MapperStatement对象前需要先创建SqlSource,BoundSql作为从mapper中解析出的sql语句的载体;BoundSql对象中的sql已经解析为带有占位符?的可执行的sql;parameterMappings是执行时传入的参数,因此每执行一次都会创建一个BoundSql对象;
public class BoundSql { private String sql; private List<ParameterMapping> parameterMappings; private Object parameterObject; private Map<String, Object> additionalParameters; private MetaObject metaParameters; public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) { this.sql = sql; this.parameterMappings = parameterMappings; this.parameterObject = parameterObject; this.additionalParameters = new HashMap(); this.metaParameters = configuration.newMetaObject(this.additionalParameters); } public String getSql() { return this.sql; } public List<ParameterMapping> getParameterMappings() { return this.parameterMappings; } public Object getParameterObject() { return this.parameterObject; } public boolean hasAdditionalParameter(String name) { String paramName = (new PropertyTokenizer(name)).getName(); return this.additionalParameters.containsKey(paramName); } public void setAdditionalParameter(String name, Object value) { this.metaParameters.setValue(name, value); } public Object getAdditionalParameter(String name) { return this.metaParameters.getValue(name); } }
在生成SqlSource对象过程中会用到XMLScriptBuilder对动态sql进行解析,转换成可执行的sql;
总结
MyBatis的初始化主要是加载解析XML配置文件,根据解析结果创建Configuration对象;将创建好的Configuration对象装载生成SqlSessionFactory实例对象;加载mappers标签过程中,会对每个mapper映射文件创建一个XMLMapperBuilder对象进行解析,在单个mapper映射文件解析过程中,会对每个sql操作标签创建一个XMLStatementBuilder对象用于解析,解析每一个sql操作生成对应的MapperStatement对象用于后续的操作;
相关文章推荐
- 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解
- 《深入理解mybatis原理》 Mybatis初始化机制详解
- 《深入理解mybatis原理》 Mybatis初始化机制具体解释
- 《深入理解mybatis原理》 Mybatis初始化机制详解
- 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)
- 《深入理解mybatis原理》 Mybatis初始化机制详解
- 深入理解MyBatis——初始化
- 《深入理解mybatis原理》 Mybatis初始化机制详解
- 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解
- 深入理解mybatis原理(一) Mybatis初始化机制详解
- 《深入理解mybatis原理(一)》 Mybatis初始化机制详解
- 《深入理解mybatis原理》 Mybatis初始化机制详解
- 深入理解java对象的创建过程:类的初始化和实例化
- 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析
- 《深入理解mybatis原理(十一)》 Mybatis插件原理之实现细节
- 深入理解 Java 虚拟机-类初始化
- 深入理解Java对象的创建过程:类的初始化与实例化
- 《深入理解mybatis原理(九)》 Mybatis数据源与连接池
- 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析
- 《深入理解mybatis原理(四)》 MyBatis的二级缓存的设计原理