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

相关文章

将Mybatis升级为Mybatis-Plus的详细过程

《将Mybatis升级为Mybatis-Plus的详细过程》本文详细介绍了在若依管理系统(v3.8.8)中将MyBatis升级为MyBatis-Plus的过程,旨在提升开发效率,通过本文,开发者可实现... 目录说明流程增加依赖修改配置文件注释掉MyBATisConfig里面的Bean代码生成使用IDEA生

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

JSON Web Token在登陆中的使用过程

《JSONWebToken在登陆中的使用过程》:本文主要介绍JSONWebToken在登陆中的使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录JWT 介绍微服务架构中的 JWT 使用结合微服务网关的 JWT 验证1. 用户登录,生成 JWT2. 自定义过滤

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

java中使用POI生成Excel并导出过程

《java中使用POI生成Excel并导出过程》:本文主要介绍java中使用POI生成Excel并导出过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录需求说明及实现方式需求完成通用代码版本1版本2结果展示type参数为atype参数为b总结注:本文章中代码均为

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI