您的位置:首页 > 其它

MyBatis原理--配置解析

2015-06-25 16:16 316 查看
MyBatis解析配置文件的入口在:

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了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: