MyBatis3源码深度解析(十四)SqlSession的创建与执行(一)Configuration与SqlSession的创建过程

本文主要是介绍MyBatis3源码深度解析(十四)SqlSession的创建与执行(一)Configuration与SqlSession的创建过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 第五章 SqlSssion的创建过程
    • 前言
    • 5.1 XPath方法解析XML文件
      • 5.1.1 XPath的基本用法
      • 5.1.2 MyBatis使用XPathParser工具类
    • 5.2 Configuration实例创建过程
    • 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 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的属性是一一对应的:

public class User {private Integer id;private String name;private Integer age;private String phone;private Date birthday;// constructor getter setter ...
}

然后编写测试代码:

示例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”元素。(简单讲,就是一路往下找)
2. 相对和绝对路径。使用“//”符号选择文档中在任何位置匹配上的节点,例如“//book[@id=‘chinese’]”会选择文档中所有ID为“chinese”的“book”元素。
3. 选取当前节点。使用“.”符号选择当前操作的节点,例如“./childnode”会选择当前节点下的“childnode”子节点。
4. 选取父节点。使用“…”符号选择当前节点的父节点,例如“…/childnode”会选择当前节点的父节点下的“childnode”子节点。
5. 选取属性。使用“@”符号选择节点的属性,例如“@id”会选择元素标签上的ID属性。
6. 选取文本。使用“text()”函数选择节点的文本内容,例如“text()=‘chinese’”会选择所有文本内容为“chinese”的节点。
7. 谓语。放在方括号中的谓语用来筛选节点,例如“//book[price>35]”会选择所有价格大于35的“book”元素。
8. 通配符。使用“”符号选择所有匹配的节点,例如“//[@id]”会选择所有ID属性不为空的节点。
9. 命名空间。使用“@”符号和命名空间前缀来指定节点的命名空间,例如“@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表达式要返回的值的类型。

源码1javax.xml.xpath.XPathConstantspublic 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工具类,可以将上面的示例代码修改成如下所示:

示例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】中编写的示例项目中,有这样一段代码:

示例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对象。

源码2org.apache.ibatis.session.SqlSessionFactoryBuilderpublic 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实例。

源码3org.apache.ibatis.builder.xml.XMLConfigBuilderpublic 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对象获取更多配置信息。

源码4org.apache.ibatis.builder.xml.XMLConfigBuilderprivate 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中有以下配置:

<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>
源码5org.apache.ibatis.builder.xml.XMLConfigBuilderprivate 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()方法。

源码6org.apache.ibatis.session.SqlSessionFactoryBuilderpublic SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}

由 源码6 可知,重载的build方法以Configuration对象为参数,创建了一个SqlSessionFactory对象,具体的落地实现类是DefaultSqlSessionFactory。

SqlSessionFactory创建完毕后,调用其openSession()方法即可创建一个SqlSession实例。

源码7org.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源码深度解析

这篇关于MyBatis3源码深度解析(十四)SqlSession的创建与执行(一)Configuration与SqlSession的创建过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/825930

相关文章

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

使用Python和Pyecharts创建交互式地图

《使用Python和Pyecharts创建交互式地图》在数据可视化领域,创建交互式地图是一种强大的方式,可以使受众能够以引人入胜且信息丰富的方式探索地理数据,下面我们看看如何使用Python和Pyec... 目录简介Pyecharts 简介创建上海地图代码说明运行结果总结简介在数据可视化领域,创建交互式地

Golang HashMap实现原理解析

《GolangHashMap实现原理解析》HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持高效的插入、查找和删除操作,:本文主要介绍GolangH... 目录HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

Python利用ElementTree实现快速解析XML文件

《Python利用ElementTree实现快速解析XML文件》ElementTree是Python标准库的一部分,而且是Python标准库中用于解析和操作XML数据的模块,下面小编就来和大家详细讲讲... 目录一、XML文件解析到底有多重要二、ElementTree快速入门1. 加载XML的两种方式2.

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

java解析jwt中的payload的用法

《java解析jwt中的payload的用法》:本文主要介绍java解析jwt中的payload的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java解析jwt中的payload1. 使用 jjwt 库步骤 1:添加依赖步骤 2:解析 JWT2. 使用 N

Python中__init__方法使用的深度解析

《Python中__init__方法使用的深度解析》在Python的面向对象编程(OOP)体系中,__init__方法如同建造房屋时的奠基仪式——它定义了对象诞生时的初始状态,下面我们就来深入了解下_... 目录一、__init__的基因图谱二、初始化过程的魔法时刻继承链中的初始化顺序self参数的奥秘默认

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析