本应在开始读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. Java中Volatile关键字详解(转载)

    转载自:https://www.cnblogs.com/zhengbin/p/5654805.html 一.基本概念 先补充一下概念:Java 内存模型中的可见性.原子性和有序性. 可见性: 可见性是 ...

  2. Centos610安装Jdk1.8

    1.下载JDK 下载:  https://pan.baidu.com/s/15TYsE_wfSb48pS4SpUQKHA 提取码:fsx6 2.上传安装包 上传到linux 并拷贝到/opt/jdk目 ...

  3. ASP.NET Core搭建多层网站架构【2-公共基础库】

    2020/01/28, ASP.NET Core 3.1, VS2019,Newtonsoft.Json 12.0.3, Microsoft.AspNetCore.Cryptography.KeyDe ...

  4. python 切片技巧

    说明: 字符串[开始索引:结束索引:步长] 开始索引:从指定位置开始截取: 结束索引:从指定位置结束截取,但不包含该位置的字符. 步长:不指定时步长为1: 1)当步长为正数时候,那么切片是从左到右进行 ...

  5. 新建文件的UID和GID

    默认情况下:新建文件的用户ID为操作当前文件进程的有效用户ID(参考以前文章),新建文件的组ID为操作当前文件的进程的有效组ID 特殊情况:当当前新建文件的目录的SET-GID位被设置时,那么新建文件 ...

  6. 【PAT甲级】1002 A+B for Polynomials (25 分)

    题意:给出两个多项式,计算两个多项式的和,并以指数从大到小输出多项式的指数个数,指数和系数. AAAAAccepted code: #include<bits/stdc++.h> usin ...

  7. netty集成springboot

    一 前言 springboot 如何集成netty实现mapper调用不为null的问题让好多读者都头疼过,知识追寻者发了一点时间做了个基本入门集成应用给读者们指明条正确的集成方式,我相信,只要你有n ...

  8. 设计模式课程 设计模式精讲 5-2 工厂方法coding

    1 课堂讲义 1.1 产品等级和产品簇 2 代码演练 2.1 工厂方法代码演练 1 课堂讲义 1.1 产品等级和产品簇 工厂方法是为了解决同一产品等级的业务抽象问题 抽象工厂方法是为了解决同一产品簇的 ...

  9. 5种JVM调优配置方法概览

    1 堆设置 -Xms:初始堆大小 -Xmx:最大堆大小 -XX:NewSize=n:设置年轻代大小 -XX:NewRatio=n:设置年轻代和年老代的比值.如:为3,表示年轻代与年老代比值为1:3,年 ...

  10. Js判断值是否是NaN

    方法一:window.isNaN() 注意: window.isNaN 只对数值有效,如果传入其他值,会被先转成数值.比如,传入字符串的时候,字符串会被先转成NaN,所以最后返回true,这一点要特别 ...