mybatis 源码解析
mybatis 初始化流程
步骤:
1、通过 ClassLoader 类加载器读取某个路径的 xml 文件来获取 InputStream 流对象.
2、通过 SqlSessionFactoryBuilder 对象来解析流, 返回工厂
3、通过 SqlSessionFactory 工厂获取 SqlSession对象
4、通过 SqlSession 可以操作. (查询、删除、修改、添加)
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
sqlSession.insert(".....");
我们通过 SqlSesseionFactoryBuilder 类来分析,它是如何通过InputStream来构建工厂的.
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null); //方法重载
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//创建XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//封装Configuration对象成SqlSessionFactory
return build(parser.parse()); //parser.parse()解析返回Configuration 对象
} 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.
}
}
}
//通过Configuration 创建 SqlSessionFactory 工厂 (此方法相当于中转)
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
可以发现 XMLConfigBuilder 对象中的parse() 方法的作用。主要是把 InputStream 解析成一个 Configuration 对象。然Configuration 对象在 mybatis 中是非常重要的。它是 mybatis 全局唯一个配置对象。通过它可以获取到所有的配置及信息。咱先暂时不解读 Configuration 对象.
XMLConfigBuilder 对象解析, 先看其构造器:
//调用的构造器(实例化 XPathParser)
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//parser 为mybatis 解析器. 暂不分析
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;
}
//解析开始的地方. 也就是我们开始解析配置文件的第一步.
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true; //标识解析已开始
parseConfiguration(parser.evalNode("/configuration")); //解析<configuration>标签
return configuration;
}
// 解析文件推荐先看下 mybatis 官方文档: http://www.mybatis.org/mybatis-3/zh/getting-started.html
private void parseConfiguration(XNode root) {
try {
//解析<properties>标签
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//解析<typeAliases>标签 别名
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 在objectFactory和objectWrapperFactory第631期之后阅读它
//解析environments节点, 如若为空配置默认的
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers 节点对应的映射文件
//解析<mappers>标签 ** 解析映射文件 (我们重点解析这个节点下的, 有兴趣的话可以看下其它的解析步骤)
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
//解析映射文件 如: TestDao.xml
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"); //获取到xml路径
String url = child.getStringAttribute("url"); //空
String mapperClass = child.getStringAttribute("class"); //空
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//获取 mapper.xml 文件InputStream
InputStream inputStream = Resources.getResourceAsStream(resource);
// 映射文件对应一个XmlMapperBuilder 对象
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 会通过 XMLMapperBuilder 对象来解析. 在构造 XMLMapperBuilder 对象会创建一个 XPathParser 对象及一个 XMLMapperEntityResolver 对象来帮助解析. 这二个对象暂时不分析, 有兴趣的话可以去了解下。 继续看源码
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
//创建了 XPathParser 与 XMLMapperEntityResolver
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() {
// 如果当前的映射文件未被加载, 就开始解析. (resource xml的命名空间)
if (!configuration.isResourceLoaded(resource)) {
//配置mapper节点下的属性
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource); //储存映射文件的命名空间
bindMapperForNamespace(); //绑定命名空间路径对应的 Class 对象
}
//解析未解决的语句及结果集对象映射
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
//mapper.xml的命名空间路径
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) { // 不设置命名空间会抛异常
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace); // 給构建器助手设置当前命名空间
//解析<mapper>标签下的 <cache-ref>标签 ======>> 不经常用不分析
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析<parameterMap>节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析<resultMap>节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析<sql>节点
sqlElement(context.evalNodes("/mapper/sql"));
//解析<select> <insert> <update> <delete> 节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
//////////////未完待续
上一篇: 0-1背包问题从复杂到简单的优化历程
下一篇: mybatis源码解析(一)