本文共 8180 字,大约阅读时间需要 27 分钟。
前言:通过前面两篇文章,我们对MyBatis有了初步的了解。从这篇开始,我会对MyBatis从初始化到执行查询的步骤进行源码的剖析。首先涉及到的就是初始化了。有疑惑可以回到。
public class MyBatisUtil { private static final SqlSessionFactory sqlSessionFactory; static{ String resource="mybatis-config.xml"; Reader reader=null; try { reader=Resources.getResourceAsReader(resource); } catch (IOException e) { e.printStackTrace(); } sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader); } public static SqlSessionFactory getSqlSessionFactory(){ return sqlSessionFactory; }}不难看出,上述代码声明了MyBatis配置文件的名称mybatis-config.xml,使用Resources的静态方法获取配置文件的输入流。有兴趣的话可以追溯一下源码看看。这里重点看一下这一条语句。
sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);点击进去查看SqlSessionFactoryBuilder的build()方法。
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 e = new XMLConfigBuilder(reader, environment, properties);
设想我们来设计MyBatis,那么在初始化的时候我们应该做什么呢?显然是解析配置文件和Mapper映射文件(mybatis-config.xml和*mapper.xml)。MyBatis的初始化也正是做了这一步。具体的代码细节涉及到了XML的解析步骤和复杂的类结构,这里只抓住主要思想。
在看第二条语句:
var5 = this.build(e.parse());这里主要是调用了XMLConfigBuilder的parse()方法。主要的调用链如下:
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; }}
private void parseConfiguration(XNode root) { try { Properties e = this.settingsAsPropertiess(root.evalNode("settings")); this.propertiesElement(root.evalNode("properties")); 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); }}XMLConfigBuilder的parse()方法中有一句this.parser.evalNode("/configuration"),这里其实就是利用了上面讲到的XPathParser对象解析XML文件的<configuration>节点(注意我们传入的文件是mybatis-config.xml配置文件,其根节点正是<configuration>)。解析出的<configuration>节点作为整个DOM的根节点被传给了XMLConfigBuilder的parseConfiguration()方法。依次解析<settings>、<properties>...<mappers>节点。
接下来以别名节点<typeAliases>为例看看这里到底做了什么?
this.typeAliasesElement(root.evalNode("typeAliases"));
private void typeAliasesElement(XNode parent) { if(parent != null) { Iterator i$ = parent.getChildren().iterator(); while(i$.hasNext()) { XNode child = (XNode)i$.next(); String alias; if("package".equals(child.getName())) { alias = child.getStringAttribute("name"); this.configuration.getTypeAliasRegistry().registerAliases(alias); } else { alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { Class e = Resources.classForName(type); if(alias == null) { this.typeAliasRegistry.registerAlias(e); } else { this.typeAliasRegistry.registerAlias(alias, e); } } catch (ClassNotFoundException var7) { throw new BuilderException("Error registering typeAlias for \'" + alias + "\'. Cause: " + var7, var7); } } }}可以看到首先对子节点的类型进行区分,对于<typeAlias>子节点,获取其别名<alias>和类型<type>属性的值之后,加载type类,并将别名和类型的映射关系存进了TypeAliasRegistry对象中,它其实是一个Map的映射结构。而从XMLConfigBuilder的父类BaseBuilder的构造器中我们可以看到,其实这个TypeAliasRegistry对象是Configuration对象中结合的对象。也就是说,<typeAliases>节点中的信息最终是被存入了Configuration对象中,其他的<configuration>子节点也是同理的。
public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();}总之,调用XMLConfigBuilder的parse()方法会把配置文件中的信息全部存入Configuration对象中。 回到刚才的语句中,继续调用SqlSessionFactoryBuilder的build()方法:
var5 = this.build(e.parse());
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);}这里做的只是将Configuration对象作为参数传递给DefaultSQLSessionFactory的构造器,并将SQLSessionFactory对象返回。
小结:MyBatis的初始化首先是将配置文件、Mapper文件和DTD文件作为输入源分别组织成Document和XMLMapperEntityResolver这种结构化的数据,再封装进XPathParser中,由XPathParser对外部提供节点的解析能力。最终,XML中的各种信息被解析并存储进了一个Configuration对象中,同时返回了一个封装了该Configuration的SQLSessionFactory对象。
private void parseConfiguration(XNode root) { try { Properties e = this.settingsAsPropertiess(root.evalNode("settings")); this.propertiesElement(root.evalNode("properties")); 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); } }
new SqlSessionFactoryBuilder().build(reader);
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;}