文章目录
- [第五章 SqlSssion的创建过程](#第五章 SqlSssion的创建过程)
-
- 前言
- [5.1 XPath方法解析XML文件](#5.1 XPath方法解析XML文件)
-
- [5.1.1 XPath的基本用法](#5.1.1 XPath的基本用法)
- [5.1.2 MyBatis使用XPathParser工具类](#5.1.2 MyBatis使用XPathParser工具类)
- [5.2 Configuration实例创建过程](#5.2 Configuration实例创建过程)
- [5.3 SqlSession实例创建过程](#5.3 SqlSession实例创建过程)
第五章 SqlSssion的创建过程
前言
MyBatis的核心组件之一SqlSession对象,表示框架与数据库建立的会话,通过该对象的实例可以完成对数据库的增删改查操作。
SqlSession对象的创建过程可以拆解为3个阶段:Configuration实例的创建过程、SqlSessionFactory实例的创建过程和SqlSession实例化的过程。
5.1 XPath方法解析XML文件
MyBatis的主配置文件和Mapper配置文件都是使用的是XML格式。
MyBatis框架在启动时,会解析这些XML配置,将配置信息转换并注册到Configuration组件中。
JDK API提供了3种方式解析XML,分别为DOM、SAX和XPath。这3种方式各有特点,MyBatis选用的方式是XPath。
因此,在研究Configuration组件的创建过程之前,有必要研究一下如何通过XPath解析XML文件。
5.1.1 XPath的基本用法
首先创建一个XML文件users.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user id="1">
<name>孙悟空</name>
<age>1500</age>
<phone>18955245635</phone>
<birthday>0000-01-01</birthday>
</user>
<user id="2">
<name>猪八戒</name>
<age>1000</age>
<phone>14577898652</phone>
<birthday>0600-10-01</birthday>
</user>
</users>
该XML文件中定义的用户信息与Java实体类User的属性是一一对应的:
java
public class User {
private Integer id;
private String name;
private Integer age;
private String phone;
private Date birthday;
// constructor getter setter ...
}
然后编写测试代码:
java
示例1
@Test
public void testXPath() throws IOException, ParserConfigurationException, SAXException, XPathExpressionException, ParseException {
// (1)创建表示XML文档的Document对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream inputStream = Resources.getResourceAsStream("users.xml");
Document document = builder.parse(inputStream);
// (2)创建用于执行XPath表达式的XPath对象
XPath xPath = XPathFactory.newInstance().newXPath();
// (3)使用XPath对象执行表达式,获取XML内容
NodeList nodeList = (NodeList) xPath.evaluate("/users/*", document, XPathConstants.NODESET);
List<User> userList = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 1; i < nodeList.getLength() + 1; i++) {
String path = "/users/user[" + i + "]";
String idStr = (String) xPath.evaluate(path + "/@id", document, XPathConstants.STRING);
String name = (String) xPath.evaluate(path + "/name", document, XPathConstants.STRING);
String ageStr = (String) xPath.evaluate(path + "/age", document, XPathConstants.STRING);
String phone = (String) xPath.evaluate(path + "/phone", document, XPathConstants.STRING);
String birthdayStr = (String) xPath.evaluate(path + "/birthday", document, XPathConstants.STRING);
User user = new User(Integer.valueOf(idStr), name, Integer.valueOf(ageStr), phone, sdf.parse(birthdayStr));
userList.add(user);
}
userList.forEach(System.out::println);
}
控制台打印执行结果:
User{id=1, name='孙悟空', age=1500, phone='18955245635', birthday=Thu Jan 01 00:00:00 CST 1}
User{id=2, name='猪八戒', age=1000, phone='14577898652', birthday=Sat Oct 01 00:00:00 CST 600}
由 示例1 可知,使用JDK提供的XPath解析XML文件的过程大致如下:
(1)创建表示XML文档的Document对象
无论通过哪种方式解析XML文件,都需要先创建表示XML文档的Document对象。
Document对象的创建依赖于DocumentBuilder对象,而DocumentBuilder对象采用工厂模式创建,因此首先需要调用DocumentBuilderFactory类的newInstance()
方法创建一个工厂类,然后调用工厂类的newDocumentBuilder()
方法创建DocumentBuilder对象,再调用DocumentBuilder对象的parse()
方法创建Document对象。parse()
方法需要XML文件的输入流作为参数。
(2)创建用于执行XPath表达式的XPath对象
XPath对象也采用工厂模式创建,因此首先要调用XPathFactory类的newInstance()
方法创建一个工厂类,然后调用工厂类的newXPath()
方法创建一个XPath对象。
(3)使用XPath对象执行表达式,获取XML内容
XPath表达式是一种在XML文档中用于定位节点的语言。它基于XML的树状结构,允许通过路径表达式来选取节点。XPath表达式可以组合使用,以实现更复杂的查找和选择。
XPath表达式的基本结构如下:
1. 节点选择。使用"/"符号从根节点开始选择,例如"/catalog/cd/price"会选择文档中根节点下的"catalog"子节点下的"cd"子节点下的"price"元素。(简单讲,就是一路往下找)
相对和绝对路径 。使用"//"符号选择文档中在任何位置匹配上的节点,例如"//book[@id='chinese']"会选择文档中所有ID为"chinese"的"book"元素。
选取当前节点 。使用"."符号选择当前操作的节点,例如"./childnode"会选择当前节点下的"childnode"子节点。
选取父节点 。使用"..."符号选择当前节点的父节点,例如".../childnode"会选择当前节点的父节点下的"childnode"子节点。
5. 选取属性。使用"@"符号选择节点的属性,例如"@id"会选择元素标签上的ID属性。选取文本 。使用"text()"函数选择节点的文本内容,例如"text()='chinese'"会选择所有文本内容为"chinese"的节点。
7. 谓语。放在方括号中的谓语用来筛选节点,例如"//book[price>35]"会选择所有价格大于35的"book"元素。通配符 。使用""符号选择所有匹配的节点,例如"// [@id]"会选择所有ID属性不为空的节点。
命名空间。使用"@"符号和命名空间前缀来指定节点的命名空间,例如"@xmlns:a='http://www.example.com/a'"会选择所有命名空间前缀为"a"的节点。
在 示例1 中,首先执行的XPath表达式是"/users/*"
,它的执行结果是一个NodeList对象,表示"users"节点下的所有节点,即2个"user"节点,因此for循环中nodeList.getLength()
的结果为2。
在for循环中,首先执行的XPath表达式是/users/user[i]/@id
,表示获取"user"节点的id属性。然后依次执行的XPath表达式是/users/user[i]/name
、/users/user[i]/age
、/users/user[i]/phone
、/users/user[i]/birthday
,分别表示获取"user"节点的"name"、"age"、"phone"、"birthday"元素。
需要注意的是,XPath对象的evaluate()
方法的最后一个由XPathConstants类指定的参数,用于设置XPath表达式要返回的值的类型。
java
源码1:javax.xml.xpath.XPathConstants
public class XPathConstants {
private XPathConstants() { }
public static final QName NUMBER = new QName("http://www.w3.org/1999/XSL/Transform", "NUMBER");
public static final QName STRING = new QName("http://www.w3.org/1999/XSL/Transform", "STRING");
public static final QName BOOLEAN = new QName("http://www.w3.org/1999/XSL/Transform", "BOOLEAN");
public static final QName NODESET = new QName("http://www.w3.org/1999/XSL/Transform", "NODESET");
public static final QName NODE = new QName("http://www.w3.org/1999/XSL/Transform", "NODE");
public static final String DOM_OBJECT_MODEL = "http://java.sun.com/jaxp/xpath/dom";
}
由 源码1 可知,XPath表达式的执行结果为XML节点对象(Node、NodeLis等)或者字符串、数值类型、布尔类型等。
5.1.2 MyBatis使用XPathParser工具类
MyBatis为了简化XPath的操作,提供了一个XPathParser工具类封装了对XML的解析操作,同时使用XNode类增强了对XML节点的操作。
因此,使用XPathParser工具类,可以将上面的示例代码修改成如下所示:
java
示例2
@Test
public void testXPathParser() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("users.xml");
// (1)创建XPathParser工具类
XPathParser parser = new XPathParser(inputStream);
// (2)调用evalNodes()方法获取所有符合表达式的节
List<XNode> nodeList = parser.evalNodes("/users/*");
for (XNode node : nodeList) {
System.out.println("--------- ");
// (3)调用getLongAttribute方法获取节点的属性
Long id = node.getLongAttribute("id");
System.out.println("id = " + id);
List<XNode> children = node.getChildren();
for (XNode childNode : children) {
// (4)调用getName和getStringBody方法获取节点的名称和值
String name = childNode.getName();
String stringBody = childNode.getStringBody();
System.out.println(name + " = " + stringBody);
}
}
}
由 示例2 可知,使用XPathParser工具类后,解析XML的逻辑变得更加简便。通过调用XPathParser工具类的evalNodes()
方法即可获取所有符合表达式的节点,而获取节点的属性只需调用XNode对象的getLongAttribute()
方法,获取节点名称调用getName()
方法,获取节点值调用getStringBody()
方法。
打开XPathParser工具类的构造方法的源码,可以发现它也是先创建DocumentBuilderFactory对象,再创建DocumentBuilder对象,最后调用DocumentBuilder的parse()
方法创建一个Document对象。这与 示例1 的写法是一致的。
5.2 Configuration实例创建过程
Configuration组件是MyBatis非常重要的核心组件之一,主要有以下3个作用:
(1)用于描述MyBatis配置信息,包括主配置文件mybatis-config.xml和Mapper配置文件;
(2)作为容器注册MyBatis的其他组件,例如TypeHandler、MappedStatement等;
(3)提供工厂方法,创建ResultSetHandler、StatementHandler、Executor、ParameterHandler等组件。
在【MyBatis3源码深度解析(十二)MyBatis的核心组件(一)Configuration】中编写的示例项目中,有这样一段代码:
java
示例3
// 读取配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 创建SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
由 示例3 可知,在SqlSession实例化之前,MyBatis会通过SqlSessionFactory的build()
方法解析主配置文件及所有Mapper配置文件,创建一个Configuration对象。
java
源码2:org.apache.ibatis.session.SqlSessionFactoryBuilder
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 创建一个XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 调用parse()方法创建Configuration实例
return build(parser.parse());
} // catch finally ...
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
由 源码2 可知,在build()
方法中会创建一个XMLConfigBuilder对象,该类的parse()
方法会返回一个Configuration实例。
java
源码3:org.apache.ibatis.builder.xml.XMLConfigBuilder
public class XMLConfigBuilder extends BaseBuilder {
private final XPathParser parser;
public Configuration parse() {
// 防止parse()方法被同一个实例调用多次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 调用XPathParser的evalNode()方法创建表示configuration节点的XNode对象
// parseConfiguration()方法对XNode对象进行处理
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// ......
}
由 源码3 可知,parse()
方法首先调用XPathParser工具类的evalNode()方法获取XML配置文件中<configuration>节点对应的XNode对象,接着调用parseConfiguration()
方法通过该XNode对象获取更多配置信息。
java
源码4:org.apache.ibatis.builder.xml.XMLConfigBuilder
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
// 处理<properties>标签
propertiesElement(root.evalNode("properties"));
// 处理<settings>标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfsImpl(settings);
loadCustomLogImpl(settings);
// 处理<typeAliases>标签
typeAliasesElement(root.evalNode("typeAliases"));
// 处理<plugins>标签
pluginsElement(root.evalNode("plugins"));
// 处理<objectFactory>标签
objectFactoryElement(root.evalNode("objectFactory"));
// 处理<objectWrapperFactory>标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 处理<reflectorFactory>标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 处理<environments>标签
environmentsElement(root.evalNode("environments"));
// 处理<databaseIdProvider>标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 处理<typeHandlers>标签
typeHandlersElement(root.evalNode("typeHandlers"));
// 处理<mappers>标签
mappersElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
由 源码4 可知,在parseConfiguration()
方法中,对于<configuration>标签的子标签,都有一个单独的处理方法,例如propertiesElement()
方法处理<properties>标签,settingsAsProperties()
方法处理<settings>标签,其它如注释所示。
这些标签具体有什么作用,参考【MyBatis3源码深度解析(十二)MyBatis的核心组件(一)Configuration】。
每个标签的解析细节,可以以处理<mappers>标签为例子来研究。
假设主配置文件mybatis-config.xml中有以下配置:
xml
<mappers>
<!--方式一:通过指定XML文件的类路径来注册-->
<mapper resource="mapper/UserMapper.xml"/>
<!--方式二:通过指定XML文件的完全限定资源定位符来注册-->
<mapper url="file:///C:\workspace\mybatis_demo2\src\main\resources\mapper\UserMapper.xml"/>
<!--方式三:通过Mapper接口的类路径来注册-->
<mapper class="com.star.mybatis.mapper.UserMapper"/>
<!--方式四:通过Mapper接口所在包路径类注册-->
<package name="com.star.mybatis.mapper"/>
</mappers>
java
源码5:org.apache.ibatis.builder.xml.XMLConfigBuilder
private void mappersElement(XNode context) throws Exception {
// 传入的参数是<mappers>标签对应的XNode对象
if (context == null) {
return;
}
// 遍历<mappers>标签的子标签
for (XNode child : context.getChildren()) {
if ("package".equals(child.getName())) {
// 如果子标签是<package>,则说明要根据包名来扫描(即方式四)
// 则将<package>标签的name属性提取出来
// addMappers方法会根据包名使用反射机制提取出包下的所有Mapper接口
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 如果子标签是<mapper>,则提取<mapper>标签的resource、url、class属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// resource属性不为空,url、class属性属性为空 -> 方式一
// 使用XMLMapperBuilder加载Mapper配置文件
ErrorContext.instance().resource(resource);
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
// url属性不为空,resource、class属性属性为空 -> 方式二
// 使用XMLMapperBuilder加载Mapper配置文件
ErrorContext.instance().resource(url);
try (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属性不为空,resource、url属性属性为空 -> 方式三
// 使用反射机制加载Mapper接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
// throw ...
}
}
}
由 源码5 可知,解析<mappers>标签的mappersElement()
针对四种可行的配置方式分别进行了处理,最终将Mapper配置文件或Mapper接口注册到Configuration组件中。其他标签的处理方法的逻辑与这相似,不再赘述。
5.3 SqlSession实例创建过程
由 示例3 可知,SqlSession实例使用工厂模式创建,因此在创建SqlSession实例之前要创建SqlSessionFactory对象。
SqlSessionFactory对象的创建依赖于SqlSessionFactoryBuilder对象,以主配置文件输入流作为参数调用其build()
方法。
由 源码2 可知,build()
方法中,首先借助XMLConfigBuilder对象对主配置文件进行解析,生成Configuration对象,然后以Configuration对象为参数,调用重载的build()
方法。
java
源码6:org.apache.ibatis.session.SqlSessionFactoryBuilder
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
由 源码6 可知,重载的build
方法以Configuration对象为参数,创建了一个SqlSessionFactory对象,具体的落地实现类是DefaultSqlSessionFactory。
SqlSessionFactory创建完毕后,调用其openSession()
方法即可创建一个SqlSession实例。
java
源码7:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
// (1)获取主配置文件中配置的环境信息
final Environment environment = configuration.getEnvironment();
// (2)创建事务管理器工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// (3)创建事务管理器
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// (4)根据主配置文件中指定的Executor类型创建对应的Executor实例
final Executor executor = configuration.newExecutor(tx, execType);
// (5)创建DefaultSqlSession实例
return new DefaultSqlSession(configuration, executor, autoCommit);
} // catch finally ......
}
由 源码7 可知,openSession()
方法主要有5个步骤,首先通过Configuration对象获取主配置文件中通过<environment>标签配置的环境信息,然后根据该标签的<transactionManager>子标签配置的事务管理器类型创建对应的事务管理器工厂,由该工厂创建对应的事务管理器。
事务管理器创建完毕后,调用Configuration对象的newExecutor()
方法,根据主配置文件中<settings>标签下的<setting name="defaultExecutorType" value="..."/>
配置指定的Executor类型创建对应的Executor实例,默认类型是SIMPLE。
最后以Configuration对象和Executor对象为参数,创建一个DefaultSqlSession对象。至此,SqlSession实例创建过程结束。
...
本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析