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

文章目录

  • [第五章 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"元素。(简单讲,就是一路往下找)

  1. 相对和绝对路径 。使用"//"符号选择文档中在任何位置匹配上的节点,例如"//book[@id='chinese']"会选择文档中所有ID为"chinese"的"book"元素。

  2. 选取当前节点 。使用"."符号选择当前操作的节点,例如"./childnode"会选择当前节点下的"childnode"子节点。

  3. 选取父节点 。使用"..."符号选择当前节点的父节点,例如".../childnode"会选择当前节点的父节点下的"childnode"子节点。
    5. 选取属性。使用"@"符号选择节点的属性,例如"@id"会选择元素标签上的ID属性。

  4. 选取文本 。使用"text()"函数选择节点的文本内容,例如"text()='chinese'"会选择所有文本内容为"chinese"的节点。
    7. 谓语。放在方括号中的谓语用来筛选节点,例如"//book[price>35]"会选择所有价格大于35的"book"元素。

  5. 通配符 。使用""符号选择所有匹配的节点,例如"// [@id]"会选择所有ID属性不为空的节点。

  6. 命名空间。使用"@"符号和命名空间前缀来指定节点的命名空间,例如"@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源码深度解析

相关推荐
Leo.yuan12 分钟前
数据库同步是什么意思?数据库架构有哪些?
大数据·数据库·oracle·数据分析·数据库架构
zhangzhangkeji12 分钟前
(33)课54--??:3 张表的 join-on 连接举例,多表查询总结。
mysql
风铃儿~13 分钟前
Spring AI 入门:Java 开发者的生成式 AI 实践之路
java·人工智能·spring
Kookoos18 分钟前
ABP VNext 与 Neo4j:构建基于图数据库的高效关系查询
数据库·c#·.net·neo4j·abp vnext
斯普信专业组18 分钟前
Tomcat全方位监控实施方案指南
java·tomcat
忆雾屿29 分钟前
云原生时代 Kafka 深度实践:06原理剖析与源码解读
java·后端·云原生·kafka
武昌库里写JAVA42 分钟前
iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出
java·开发语言·spring boot·学习·课程设计
云之兕44 分钟前
MyBatis 的动态 SQL
数据库·sql·mybatis
gaoliheng0061 小时前
Redis看门狗机制
java·数据库·redis
我是唐青枫1 小时前
.NET AOT 详解
java·服务器·.net