Mybatis构建SqlSessionFactory的过程
概览
- 常用的解析XML的方式
- Java 解析XML案例代码
- mybatis解析的具体过程
- 总结
常用的解析XML的方式
mybatis是基于XML进行构建的,所以我们可以首先了解下,java中解析XML的几种方式
- DOM 方式:DOM 基于树形结构解析, 它会将整个文档读入内存并构建一个 DOM 树, 基于这棵树的结构对各个节点进行解析;
- SAX 方式:SAX 是基于事件模型的 XML 解析方式, 它不需要将整个 XML 文档加载到内存中, 而只需要将一部分 XML 文档的一部分加载到内存中, 即可开始解析;
- StAX 方式:StAX 与 SAX 类似, 也是把 XML 文档作为一个事件流进行处理, 但不同之处在于 StAX 采用的是“拉模式”, 即应用程序通过调用解析器推进解析的过程。
mybatis在加载mybatis-config.xml的时候,使用的是DOM的Xpath方式进行XML的解析。
下面就是一个java解析XML的案例
Java 解析XML案例代码
student.xml
<STUDENT>
<CD id="1">
<NAME>ZHANGSAN</NAME>
</CD>
<CD id="6">
<NAME>LISI</NAME>
</CD>
<CD id="8">
<NAME>WANGER</NAME>
</CD>
<CD id="10">
<NAME>MAZI</NAME>
</CD>
<CD id="13">
<NAME>FENQING</NAME>
</CD>
</STUDENT>
Parse.java
import org.apache.ibatis.io.Resources;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.io.InputStream;
/**
* @version v1.0
* @ProjectName: mybatis-sources
* @ClassName: Parse
* @Description: TODO(一句话描述该类的功能)
* @Author: Aby1993
* @Date: 2020/4/7 21:42
*/
public class Parse {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setValidating(false);
builderFactory.setNamespaceAware(false);
builderFactory.setIgnoringComments(true);
builderFactory.setIgnoringElementContentWhitespace(false);
builderFactory.setCoalescing(false);
builderFactory.setExpandEntityReferences(true);
DocumentBuilder builder = builderFactory.newDocumentBuilder();
builder.setErrorHandler(new ErrorHandler() {
@Override
public void warning(SAXParseException exception) throws SAXException {
System.out.println("warning:" + exception.getMessage());
}
@Override
public void error(SAXParseException exception) throws SAXException {
System.out.println("error:" + exception.getMessage());
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
System.out.println("fatalError:" + exception.getMessage());
}
});
String resource = "student.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
Document document = builder.parse(inputStream);
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
//获取CD->NAME的数值
XPathExpression expression = xPath.compile("//CD//NAME//text()");
Object result = expression.evaluate(document, XPathConstants.NODESET);
NodeList nodeList = (NodeList) result;
for (int i = 0; i < nodeList.getLength(); i++) {
System.out.println(nodeList.item(i).getNodeValue());
}
}
}
执行结果
ZHANGSAN
LISI
WANGER
MAZI
FENQING
关于java解析XML网上有不少案例,作为基础,这一块儿还是应该重点掌握的,我本人对这一块儿也是不太熟,还是因为看源码了,各种解析XML的过程,看了相关的文档和博文,才算是渐渐的稍微理解一点儿,想要掌握还需要再接再厉才行。
mybatis解析的具体过程
测试代码
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//虽然说解析流是为了获取SqlSessionFactory ,但是实际上其实是为了获取里面的Configuration
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
根据SqlSessionFactoryBuilder().build(inputStream)方法进入到对应的org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)方法中,代码如下:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
注意XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);方法进去,有两个比较重要的方法,需要大家仔细看看,可以看到其中的方法明显有上面我们解析XML案例中的一些代码,具体细节我们就不讲了:
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
//注意看看实例化了什么
commonConstructor(validation, variables, entityResolver);
//可以看到有如同上方案例的代码
this.document = createDocument(new InputSource(inputStream));
}
通过parser.parse()我们进入到方法org.apache.ibatis.builder.xml.XMLConfigBuilder#parse中,具体代码如下:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//获取mybatis-config.xml中configuration节点下的数据
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
我们通过代码parseConfiguration(parser.evalNode("/configuration"));进入到org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration方法中,主要是解析配置文件中各个节点的数据,代码如下:
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
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);
}
}
对于以上的解析过程,我们只看一个解析mapper的过程,通过代码 mapperElement(root.evalNode("mappers")); 我们进入到org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement方法中,代码如下:
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");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
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) {
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.");
}
}
}
}
}
通过以上代码,我们能够看到,mybatis在解析mapper时候的解析顺序,以及将mapper接口和mapper.xml之间通过namespace关联起来的一个过程,具体在方法org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace中,具体代码如下:
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
总结
好了,今天这个mybatis解析XML的过程,就说到这里,我们能够看到,虽然说SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);这句代码最终获取的是SqlSessionFactory,但是实际上整个过程,就是装配Configuration的过程,所以获取SqlSessionFactory是现象,而装配Configuration是本质。另外虽然说mybatis源码相对简单一些,但是我们还是能够看到想要完全看懂还是有点儿困难,对我来说,所谓的框架就是完全灵活运用了java基础和各种设计模式的产物,框架看的吃力,说明我们对于java基础和设计模式掌握的还是不够,所以知道问题在哪里后,我们需要在平时多花点儿时间巩固基础,对于mybatis还没有用过的朋友,可以参考中文网站mybatis网站,好了,就这些。