您的位置:首页 > 其它

深入理解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对象用于后续的操作;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: