本应在开始读MyBatis源码时首先应该了解下MyBatis的SqlSession的四大对象:Executor、StatemenHandler、ParameterHandler、ResultHandler,但我想把这四大对象放到我们源码中一步一步来解读。

开始。


对MyBatis的使用我们在最开始都已经知道可以通过xml配置文件的方式,也可以通过Java代码创建Configuration对象的方式。这两者实际上是一样,xml配置文件的方式最终也是通过解析xml配置文件创建一个Configuration对象。可能对于很多人来说MyBatis通常是和Spring配合使用,用了N年MyBatis也不能把MyBatis说个所以出来。写MyBatis的这个系列,正式希望不要只光会用,还要懂其原理,熟悉一个语言、一个框架的特性原理才能在不同场合使用不同的特性。

回到正题,我们说到使用MyBatis第一步就是配置,或者说第一个重要的对象就是Configuration。但我想要阅读的第一个源码并不是Configuration类,我们暂且知道它会贯穿整个MyBatis的生命周期,它是存放一些配置所在的地方即可。我们要说的是MyBatis第二个重要部分——SqlSession的执行过程。

从官方文档的说明,我们可以知道SqlSession是由SqlSessionFactory创建,SqlSessionFactoryBuilder创建。

我们通过代码简单回顾一下SQLSession实例的创建过程。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

在创建一个SqlSession实例时,首先需要创建一个SqlSessionFactory实例,而又需要通过SqlSessionFactoryBuilder()的build静态方法来创建SqlSessionFactory。(关于这三者的作用域(Scope)及生命周期之前有介绍过,这里不再多讲,参考《SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession作用域(Scope)和生命周期》

所以我们首先来看看SqlSessionFactoryBuilder这个类。它放置在package org.apache.ibatis.session包中。

 1 public class SqlSessionFactoryBuilder {
2
3 public SqlSessionFactory build(Reader reader) {
4 return build(reader, null, null);
5 }
6
7 public SqlSessionFactory build(Reader reader, String environment) {
8 return build(reader, environment, null);
9 }
10
11 public SqlSessionFactory build(Reader reader, Properties properties) {
12 return build(reader, null, properties);
13 }
14
15 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
16 try {
17 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
18 return build(parser.parse());
19 } catch (Exception e) {
20 throw ExceptionFactory.wrapException("Error building SqlSession.", e);
21 } finally {
22 ErrorContext.instance().reset();
23 try {
24 reader.close();
25 } catch (IOException e) {
26 // Intentionally ignore. Prefer previous error.
27 }
28 }
29 }
30
31 public SqlSessionFactory build(InputStream inputStream) {
32 return build(inputStream, null, null);
33 }
34
35 public SqlSessionFactory build(InputStream inputStream, String environment) {
36 return build(inputStream, environment, null);
37 }
38
39 public SqlSessionFactory build(InputStream inputStream, Properties properties) {
40 return build(inputStream, null, properties);
41 }
42
43 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
44 try {
45 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
46 return build(parser.parse());
47 } catch (Exception e) {
48 throw ExceptionFactory.wrapException("Error building SqlSession.", e);
49 } finally {
50 ErrorContext.instance().reset();
51 try {
52 inputStream.close();
53 } catch (IOException e) {
54 // Intentionally ignore. Prefer previous error.
55 }
56 }
57 }
58
59 public SqlSessionFactory build(Configuration config) {
60 return new DefaultSqlSessionFactory(config);
61 }
62
63 }

我们可以看到这个类用很多的构造方法,但主要分为三大类:1、第3-29行是通过读取字符流(Reader)的方式构件SqlSessionFactory。2、第31-57行是通过字节流(InputStream)的方式构件SqlSessionFacotry。3、第59行直接通过Configuration对象构建SqlSessionFactory。第1、2种方式是通过配置文件方式,第3种是通过Java代码方式。

让我们再仔细来看到底是怎么构建出SqlSessionFactory的呢?以通过InputStream字节流的方式来看,和它相关的一共有4个构造方法,其中最后一个public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties),第2个和第3个参数并不陌生,这相当于在告诉这两个配置项environment、properties是可以通过在构建SqlSessionFactory的时候进行配置的或重新配置(此时优先级最高)。首先通过第45-46行代码XMLConfigBuilder工具类对配置文件进行解析成Configuration对象,再调用public SqlSessionFactory build(Configuration config)构建出SqlSessionFactory,所以兜兜转转,不管是配置文件还是Java代码,最后都会经过解析通过Configuration对象产生SqlSessionFactory。

我们可以发现第60行代码返回的是DefaultSqlSessionFactory实例,而不是SqlSessionFactory。那是因为实际上SqlSessionFactory是一个接口,而DefaultSqlSessionFactory是它的实现类。如下图所示。

在这里我们暂且不管SqlSessionManager,我们只需知道SqlSessionFactory有DefaultSqlSessionFactory和SqlSessionManager。在SqlSessionFactory可以猜测一下有什么方法。

回顾SqlSession的创建过程,其实我们也能猜测得到SqlSessionFactory一定主要是创建SqlSession实例的方法。

 1 public interface SqlSessionFactory {
2
3 SqlSession openSession();
4
5 SqlSession openSession(boolean autoCommit);
6 SqlSession openSession(Connection connection);
7 SqlSession openSession(TransactionIsolationLevel level);
8
9 SqlSession openSession(ExecutorType execType);
10 SqlSession openSession(ExecutorType execType, boolean autoCommit);
11 SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
12 SqlSession openSession(ExecutorType execType, Connection connection);
13
14 Configuration getConfiguration();
15
16 }

这么多的openSession重载方法,都是通过传入不同的参数构造SqlSession实例,有通过设置事务是否自动提交"autoCommit",有设置执行器类型"ExecutorType"来构造的,还有事务的隔离级别等等。最后一个方法就告诉我们可以通过SqlSessionFactory来获取Configuration对象。至于DefaultSqlSessionFactory对SqlSessionFactory的具体实现,除了以上方法之外,还包括了:openSessionFromDataSource、openSessionFromConnection、getTransactionFactoryFromEnvironment、closeTransaction。到这里我们似乎还是只停留在表面,并没有涉及相对比较底层的代码啊,别急。我们这是刚走了一遍“SqlSession创建过程”的流程。下面我们从SqlSessionFactoryBuilder第60行return new DefaultSqlSessionFactory(config)开始。


由于SqlSessionFactory的实现类DefaultSqlSessionFactory,源码过长,我们在其中以截取关键的代码作为解读。

DefaultSqlSessionFactory中的第1行代码实际上就非常值得我们思考:final关键字。

private final Configuration configuration;

为什么会使用final关键字对Configuration对象进行修饰呢?Configuration应该是存在于MyBatis的整个生命周期那么意味着它应该是有且仅有一个实例的,而final关键字修饰的变量字段就代表它是不可变对象《“不可变的对象”与“不可变的对象引用”》),这也恰好能解释说明官方User Guide中所说的SqlSessionFactory应该是单例的。但这是设计在前?还是规则在前呢?如果是设计在前,那为什么这样设计?如果是规则在前,是什么样的规则规定了这样做呢?我认为是设计在前。

首先,MyBatis认为配置文件之所以是配置文件那么就以为着它只有一种配置(这个说法并不是很全面,因为我们已经见到了那么多的构造方法就说明在一个应用程序中可以通过不同的场景配置选用不同的配置,事实也如此),就好比我们将一个新手机买回来过后,设置时间、日期就不再去更改,但我们可能会出国,这个时候就要配置选用另一个时区的时间,不过我还是使用的是这个手机的设置,换句话说,你的手机不可能有两个系统设置吧。所以Configuration对象实际上就是我们手机上的系统设置。而SqlSessionFactory是通过Configuration来构造SqlSession的,对Configuration的引用当然是不可变的,如果可变,那相当于你手机里岂不是可以新建一个系统设置?那不就乱套了?索性final,对象不可变。此时也就建议SqlSessionFactory是单例的了,你构建N个SqlSessionFactory,它们也是通过一个Configuration对象来构造的SqlSession实例,那还有必要有N个SqlSessionFactory了吗?显然没有必要,所以最好就是将SqlSessionFactory设计为单例。同样可参考《SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession作用域(Scope)和生命周期》

这才对DefaultSqlSessionFactory类第一句话进行了解读,接着就是实现SqlSessionFactory接口的8个构造方法。DefaultSqlSessionFactory并没有直接实现这8个构造方法而是调用另外两个新的方法,这8个构造方法实际上分为两大类:一个是从数据源中获取SqlSession,一个是从Connection中获取SqlSession(包含Connection参数的那两个构造函数)。

先看从数据源中获取SqlSession。

 1 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
2 Transaction tx = null;
3 try {
4 final Environment environment = configuration.getEnvironment();
5 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
6 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
7 final Executor executor = configuration.newExecutor(tx, execType);
8 return new DefaultSqlSession(configuration, executor, autoCommit);
9 } catch (Exception e) {
10 closeTransaction(tx); // may have fetched a connection so lets call close()
11 throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
12 } finally {
13 ErrorContext.instance().reset();
14 }
15 }

如果没有传入ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit这三个参数就代表使用我们Configuration对象中的配置(看来Executor、TransactionIsolationLevel、autoCommit是可以灵活配置的)。第8行创建出一个DefaultSqlSession实例,可以猜测SqlSession是一个接口而DefaultSqlSession是其实现类。对于SqlSession的创建过程,我们马上就要走到最后一步SqlSession的构建。而这也是最关键最重要最发杂的一步。

SqlSession为什么可以提交事务的更多相关文章

  1. Mybatis详解系列(一)--持久层框架解决了什么及如何使用Mybatis

    简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...

  2. Mybatis系列全解(六):Mybatis最硬核的API你知道几个?

    封面:洛小汐 作者:潘潘 2020 年的大疫情,把世界撕成几片. 时至今日,依旧人心惶惶. 很庆幸,身处这安稳国, 兼得一份安稳工. · 东家常讲的一个词:深秋心态 . 大势时,不跟风.起哄, 萧条时 ...

  3. 第一个基础框架 — mybatis框架 — 更新完毕

    1.Mybatis是什么? 百度百科一手 提取一下重点: MyBatis 本是apache的一个开源项目iBatis.即:mybatis的原名为:ibatis 2010年迁移到google code, ...

  4. 注解,lombok

    使用注解开发 UserMapper public interface UserMapper { @Select("select * from db4.user") List< ...

  5. mybatis中autoCommit自动提交事务

    今天学习了下mybatis, 对其中的autoCommit自动提交事务比较好奇, 研究了下,把配置和代码都放上 mapper.xml如下: <?xml version="1.0&quo ...

  6. SSM-MyBatis-07:Mybatis中SqlSession的insert和delete底层到底做了什么

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 先点进去看一下insert方法 用ctrl加鼠标左键点进去看 发现是一个接口SqlSession的方法,没有实 ...

  7. Sqlsession 的理解

    MyBatis的持久化解决方案是将用户从原始的JDBC访问中解放出来,用户只需要定义需要操作的SQL语句,无须关注底层的JDBC操作,就可以以面向对象的方式来进行持久化层操作.底层数据库连接的获取,数 ...

  8. spring中的mybatis的sqlSession是如何做到线程隔离的?

    项目中常常使用mybatis配合spring进行数据库操作,但是我们知道,数据的操作是要求做到线程安全的,而且按照原来的jdbc的使用方式,每次操作完成之后都要将连接关闭,但是实际使用中我们并没有这么 ...

  9. SqlSession介绍

    SqlSession是MyBatis的关键对象,是执行持久化操作的对象,类似于JDBC中的Connection.它是应用程序与持久存储层之间执行交互操作的一个单线程对象,也是MyBatis执行持久化操 ...

随机推荐

  1. 1、MyBatis框架底层初涉

    1.拜年 哈哈,现在是过年了,祝大家新年好. 本来大过年的是不打算碰电脑的,(抢票除外,三疯同学现在还没抢到票,然后突然又延长假期了).现在疫情严重,被堵家里不能出去了.不能为国家做贡献,但是起码不能 ...

  2. HDU2612 Find a way (跑两遍BFS)

    Pass a year learning in Hangzhou, yifenfei arrival hometown Ningbo at finally. Leave Ningbo one year ...

  3. 迭代器对象numpy.nditer在数组上进行迭代——修改数组的值

    nditer对象有另一个可选参数op_flags,默认情况下,nditer将视待迭代遍历的数组为只读对象(read-only),为了在遍历数组的同时,实现对数组元素值得修改,必须指定op_flags= ...

  4. VUE父子组件相互传值

    passer.vue中代码 首先在文件中引入组件 import canvasDraw from '@/components/CanvasDraw/canvasDraw' 局部注册组件:componen ...

  5. pycharm 右键无法显示unittest框架&&解决右键只有unittest 运行如何取消右键显示进行普通run

    上面是普通文件和unittest 导入的文件右键快捷键显示情况,可以看出两者快捷键都是ctr+shift+F10,如果你是右键模式想运行unitest,但是又不知道哪里配置unittest直接运行快捷 ...

  6. [IDEA] Idea复制文件到项目一直updating indices的问题

    通常我们在开发JavaWeb项目的时候,都需要先将网页写好,在进行复制到web目录下,如果里面包含了很多的资源文件,就会造成一直updating indices. 方法一: 这是因为项目需要对web目 ...

  7. 图书商城(基于Jsp+Servlet)

    这个项目主要是加深一下对于servlet和jsp知识相关的了解以及简单业务逻辑的处理. 用户更新的逻辑: 1.点击修改用户的那一行可以获取到用户的id 2.跳转到一个servlet,去查询该用户的基本 ...

  8. cmake学习资料收集

    CMake 学习笔记 : https://www.jianshu.com/p/c417e4ab8b30

  9. oracle 高级函数

    原 oracle 高级函数 2017年08月17日 16:44:19 阅读数:1731 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u013278 ...

  10. 1.项目创建及集成git

    1.创建项目 (1)创建完以后,打开Terminal命令行,输入"git init"用于初始化本地仓库 (2)打开对应的项目文件夹,“Ctrl+h”可以查看该文件夹里是否存在.gi ...