文章目录
前言
接上文我们在环境搭建好演示能跑通的情况下开始进行源码的跟踪和分析,首先我们在sqlSessionFactory 创建的关键行键入断点跟踪一下sqlSessionFactory 到底是如何构建的, 我们要知道SqlSessionFactoryBuilder是构建阶段的调用入口类。它会调用XMConfigBuilder构建配置。XMLConfigBuilder会调用XMLMapperBuilder(以 XML 形式定义 SQL Mapper时)构建SQLMapper映射。SqlSessionFactoryBuilder在得到初始化的configuration对象后用其构建SqlSessionFactory。而SqlSessionFactory 是生产 SqlSession 对象的工厂,SqlSession 则是 MyBatis 执行阶段的关键入口类。通过上简短的描述我们大致对构建顺序有了一个初步的印象。接下来我们将深入源码跟踪其执行过程看看mybatis-config.xml配置文件是如何一步一步被解析最终构建出SqlSession 对象的。
public class RunDemo {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Student student = sqlSession.selectOne("org.apache.ibatis.demo.StudentMapper.selectByUserName","刘亦菲");
System.out.println(student.toString());
}
}
一、XmlConfigBuilder的初始化
准备
进入SqlSessionFactory 的build方法后首先看到的是许多不同参数的重载build()方法,通过build(InputStream inputStream)底层进入到了build(InputStream inputStream, String environment, Properties properties),这个方法才是关键的开始。在众多的build()方法中核心的两个就是build(inputStream,environment,properties)与build(config)下面我们会详细展开第一个方法,第二个方法的使用大致是在 configuration 对象解析完成后使用 configuration对象构建DefaultSqlSessionFactory对象。这里不详细展开。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 初始化解析器
return build(parser.parse()); // 会将构建好的config回传
} 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.
}
}
}
MyBatis只支持XML形式的Configuration配置,此时首先创建了一个XMLConfigBuilder 对象,因为此时还没有对配置文件进行解析所以构造函数传入的environment,properties暂时都为null。接着我们进入parse()方法探究下mybatis到底是如何解析配置文件的。
这里parsed变量默认是false第一次进入方法会判断是否是初次调用,如果不是则抛出异常。下面也将进入到我们最关键的方法parseConfiguration(), 首先设置了从根节点开始解析,为什么是根节点你有什么依据?说到这里我们就不得不提到mybatis中的 .dtd与.xsd文件了。
public Configuration parse() {
/**
* 若已经解析过了就抛出异常,防止parse()方法被同一个实例多次调用
*/
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
/**
* 设置解析标志位
*/
parsed = true;
/**
* 解析我们的mybatis-config.xml的
* 节点
* <configuration>
* </configuration>
*
* 调用XPathParser对象的evalNode()方法获取XML配置文件中<configuration>节点对应的XNode对象
*/
parseConfiguration(parser.evalNode("/configuration")); // 获取配置文件根节点的configuration元素,再调用parseConfiguration()完成解析操作
return configuration;
}
下面简单看一段dtd和xsd文件的解释:
DTD文档类型定义(Document Type Definition)DTD 是一套关于标记符的语法规则。它是XML1.0版规格得一部分,是XML文件的验证机制,属于XML
文件组成的一部分。DTD 是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正
确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。XML文件提供应用程序一个数据交
换的格式,DTD正是让XML文件能够成为数据交换的标准,因为不同的公司只需定义好标准的DTD,各公司都能够依照DTD建立XML文件,并且进行验证,
如此就可以轻易的建立标准和交换数据,这样满足了网络共享和数据交互。DTD文件是一个ASCII的文本文件,后缀名为.dtd。
xsd
可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过XML Schema指定一个XML文档所允许的结
构和内容,并可据此检查一个XML文档是否是有效的。XML Schema本身是一个XML文档,它符合XML语法结构。可以用通用的XML解析器解析它。一个
XML Schema会定义:文档中出现的元素、文档中出现的属性、子元素、子元素的数量、子元素的顺序、元素是否为空、元素和属性的数据类型、元素或
属性的默认和固定值。XSD是DTD替代者的原因,一是据将来的条件可扩展,二是比DTD丰富和有用,三是用XML书写,四是支持数据类型,五是支持命名
空间。
看完简单的介绍后可能此时也明白了为什么我们的xml文件头中要引入dtd文件其作用真是约束我们xml编写时的规范,它的作用是验证文档的元素和规则列表。同时我们也能通过dtd文件了解到xml的规范信息,从中确定即为mybatis配置文件的根节点。这些都不是我们关注的关键简单了解就好。在知道了根元素后我们就有了目标,可以从根元素向内递归的进行解析。然后将解析到的值赋值给configuration对象
解析configuration标签
继续回到源码中我们可以看到在 parseConfiguration()方法中先执行了parser.evalNode("/configuration")这方法又为我们做了什么事情呢?
首先XPathParser 是mybatis基于 JDK 自带的 DOM 工具实现的解析器。它底层调用的是JDK种的XPath(JDK API中提供了3种方式解析XML,分别为DOM、SAX和XPath)来进行对XML文件的解析。这里我们不进行展开只需要知道它是做什么的,在构建过程中起到什么作用即可。
XNode类对应了配置文件中一个元素节点的信息。
/**
* org.w3c.dorn.Node对象
*/
private final Node node;
/**
* Node节点名称
*/
private final String name;
/**
* 节点的内容
*/
private final String body;
/**
* 节点属性集合
*/
private final Properties attributes;
/**
* 配置文件中<properties>节点下定义的键位对
*/
private final Properties variables;
/**
* XPathParser对象,当前XNode对象由此XPathParser对象生成
*/
private final XPathParser xpathParser;
下面我们简单探究下Xnod对象结构其中成员node属性的实现类是DeferredElementImpl,而DeferredElementImpl又继承于ElementImpl一直往上知道Node接口。通过这样层层继承的关系到Xnode对象中的node属性时才具有了如下众多的成员属性。通过不断的递归就完整解析出了标签下的文档树。
二、解析标签的子节点完成configuration对象赋值
propertiesElement(root.evalNode(“properties”))
首先最先解析的当然是properties标签,该方法会生成一个 java.util.Properties对象defaults,并将其设置到 parser和configuration对象中。从源码我们可以看出首先会去解析下是否有子标签。如果有则解析存入Properties类对象defaults中
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
接着会解析中是否存在resource或url。这里规定只能存在其一否则就会抛出BuilderException异常,后面就比较简单了像本文实例将解析出的resource信息装载到了parse和configuration对象中。到这里第一个方法propertiesElement()的任务就算完成了,成功将解析出的标签信息载入到了configuration对象中。后面方法所做的事情基本一致在将所有标签信息解析装配后成功返回configuration对象。最后再回到build()重载方法通过configuration参数的方法成功返回SqlSessionFactory对象
拿到SqlSessionFactory对象获取sqlSession
紧接着我们需要通过SqlSessionFactory调用openSession()无参方法获取sqlSession。
获取事务工厂创建事务管理对象
进入到openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit)方法后首先会去获取configuration对象中的环境变量,并通过环境变量来获取事务工厂。此时我们因为在 中设置了事务工厂类型为jdcc所以此时事务工厂是jdbcTransactionFactory,如果这里为空的话会为我们自动创建一个ManagedTransactionFactory对象。他们两者的区别如下:
(1)JdbcTransaction直接使用JDBC的提交和回滚事务管理机制 。它依赖与从dataSource中取得的连接connection 来管理transaction 的作用
域,connection对象的获取被延迟到调用getConnection()方法。如果autocommit设置为on,开启状态的话,它会忽略commit和rollback。直观
地讲,就是JdbcTransaction是使用的java.sql.Connection 上的commit和rollback功能,JdbcTransaction只是相当于对java.sql.Conne
ction事务处理进行了一次包装(wrapper),Transaction的事务管理都是通过java.sql.Connection实现的。
(2)使用MANAGED的事务管理机制,这种机制mybatis自身不会去实现事务管理,而是让程序的容器(JBOSS,WebLogic)来实现对事务的管理
注意:如果我们使用MyBatis构建本地程序,即不是WEB程序,若将type设置成"MANAGED",那么,我们执行的任何update操作,即使我们最后执行了
commit操作,数据也不会保留,不会对数据库造成任何影响。因为我们将MyBatis配置成了“MANAGED”,即MyBatis自己不管理事务,而我们又是运行
的本地程序,没有事务管理功能,所以对数据库的update操作都是无效的。
创建sql执行器对象
下面就进入我们sqlSession获取最关的步骤创建sql执行器在我们进行sql工作的时候底层就是executor帮我们做的,继续跟踪到newExecutor()探究mybatis到底做了那些工作。因为在配置文件中我们没有开启缓存配置所以这里defaultExecutorType就是simple。
在mybatis枚举类ExecutorType中跟别定义了简单的sql执行器对象,一次性的执行器和可重复使用的执行器三种类型。在进行一列简单判断后,就会为我们创建SimpleExecutor对象。但是在SimpleExecutor构造函数中它其实是调用的父类BaseExecutor构造函数。所以我们直接进入BaseExecutor中看下基础执行器到底包含了哪些内容。这里我们不对BaseExecutor中执行方法进行展开,留在下章sql映射与执行环节进行详细研究和说明。成功返回执行器对象
/**
* @author Clinton Begin
*/
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
//事务对象
protected Transaction transaction;
//执行权包装对象
protected Executor wrapper;
//延时加载队列
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
//一级缓存
protected PerpetualCache localCache;
//本地输出类型的参数的缓存
protected PerpetualCache localOutputParameterCache;
//mysql全局配置文件
protected Configuration configuration;
//记录嵌套查询的层级
protected int queryStack;
//是否关闭
private boolean closed;
注:上图中cacheEnable默认为true即mybatis的一级缓存默认是开启的
DefaultSqlSession(configuration, executor, autoCommit)返回DefaultSqlSession对象
到这里最后一步成功获取到sqlsession对象,接着我们就可以通过sqlsession借助executor执行我们的sql语句了,本篇到这里结束下章接着学习mapper映射跟sql执行原理。
总结
1.通过resource获取mybatis-config.xml文档对象
2.解析mybatis-config.xml将其中的配置信息载入到configuration对象中
3.从configuration中获取环境和配置等信息获取事务工厂对象从而创建出事务对象(事务与执行类型是执行对象必不可少的元素)
4.创建一个sql执行器对象
5.返回 DefaultSqlSession(configuration, executor, autoCommit)