mybatis源码_Mybatis源码之SqlSession

SqlSession简介

Mybatis是一个强大的ORM框架,它通过接口式编程为开发者屏蔽了传统JDBC的诸多不便,以简单的方式提供强大的扩展能力。其中的接口式编程就是指日常使用的Mapper接口,Mybatis借助动态代理实现了sql语句与Mapper的接口的动态绑定,降低复杂度的同时为开发人员提供强大自主权。

经过上一篇文章《Mybatis源码之SQL执行过程》已经知道,Mapper其实是Mybatis暴露给开发人员的扩展能力,在运行时Mybatis把Mapper接口转为动态方法,最终也会转交给SqlSession的CRUD方法执行。所以,SqlSession是Mybatis框架真正意义上的命令执行入口。

SqlSession是Mybatis中最基础的一个接口类,它代表一个与数据库的会话,在其生命周期内完成与数据库的交互,通过它可以:

  • 执行基本的SQL命令(select/update/insert/delete):SQL命令具有唯一ID,每个ID对应Configration中的一个MappedStatement。

  • 获取自定义的Mapper接口对象(getMapper):使用动态代理技术动态生成Mapper接口实现类,由MapperProxy代理实现。

  • 控制管理事务操作(rollback/commit):数据库事务操作中提交与回滚操作。

49deaf2e72fad9a8f1700febd7a6fc43.png
image.png

以上类图为SqlSession等Mybatis接口层的继承体系与关系,由上图可知:

  • SqlSessionFactory负责SqlSession的创建工作,其默认实现为DefaultSqlSessionFactory。

  • SqlSession有两个实现类:DefaultSqlSession和SqlSessionManager,其中DefaultSqlSession为默认实现;SqlSessionManager除了实现SqlSession接口外,还实现了SqlSessionFactory接口。

接下来,我们通过实例和源码来详细了解下DefaultSqlSession和SqlSessionManager两者的特点与差别,进一步加深对它的理解。

DefaultSqlSession

DefaultSqlSession是SqlSession的默认实现,它纯粹是一个功能类,完全实现了SqlSession的接口方法,但是它并不具备自主管理能力。在开始之前,我们先了解两个问题:

  • DefaultSqlSession是非线程安全的,因此需要避免多线程并发使用,随用随取,及时释放;

  • DefaultSqlSession它不具有自动关闭的能力,需要开发者自主调用关闭方法。

下面这个示例代码还是跟之前一样,我们重点关注第7-9行,其实只有两行代码。

 1public static void main(String[] args) throws IOException {
2    // mybatis配置文件
3    String path = "mybatis-config.xml";
4    InputStream inputStream = Resources.getResourceAsStream(path);
5
6    // 获取 SqlSessionFactory
7    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
8    // 创建 SqlSession
9    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
10        // ……
11    }
12}

第7行:主要完成配置文件解析并保存到Configuration,由SqlSessionFactoryBuilder创建SqlSessionFactory对象,为创建SqlSession做准备。

第9行:采用“try-with-resource”方式调用SqlSessionFactory#openSession方法创建了SqlSession对象。当代码离开try代码块时会自动关闭SqlSession,但是这里的自动关闭并不是SqlSession的能力,相当于手动调用了SqlSession#close方法。看下openSession的实现:

 1  public SqlSession openSession() {
2    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
3  }
4
5  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
6    Transaction tx = null;
7    try {
8      //获取环境配置信息
9      final Environment environment = configuration.getEnvironment();
10      //创建事务处理器
11      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
12      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
13      //创建Executor,这里默认使用CachingExecutor
14      final Executor executor = configuration.newExecutor(tx, execType);
15      //创建SqlSession,默认实例DefaultSqlSession
16      return new DefaultSqlSession(configuration, executor, autoCommit);
17    } catch (Exception e) {
18      closeTransaction(tx); // may have fetched a connection so lets call close()
19      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
20    } finally {
21      ErrorContext.instance().reset();
22    }
23  }

由此可知,openSessionFromDataSource方法完成了SqlSession的创建,它返回了DefaultSqlSession对象。DefaultSqlSession中聚合了Configuration、Executor等对象。

selectXxx方法

以selectList为例看下selectXxx方法的执行过程:

 1  public  List selectList(String statement, Object parameter, RowBounds rowBounds) { 2    try { 3      //statement为MappedStatement的唯一id,注册在Configuration#mappedStatements 4      //根据id获取MappedStatement 5      MappedStatement ms = configuration.getMappedStatement(statement); 6      //调用执行器的query方法执行查询命令。 7      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 8    } catch (Exception e) { 9      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);10    } finally {11      ErrorContext.instance().reset();12    }13  }

SqlSession中selectXxx方法,最终都走到了selectList方法,但是它并没有直接与数据库交互,获取MappedStatement对象后调用了执行器Executor的query方法。

insert方法

insert对应sql的insert命令,代码如下:

 1  public int insert(String statement) {
2    return insert(statement, null);
3  }
4
5  public int insert(String statement, Object parameter) {
6    return update(statement, parameter);
7  }
8
9  public int update(String statement, Object parameter) {
10    try {
11      dirty = true;
12      MappedStatement ms = configuration.getMappedStatement(statement);
13      return executor.update(ms, wrapCollection(parameter));
14    } catch (Exception e) {
15      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
16    } finally {
17      ErrorContext.instance().reset();
18    }
19  }

与select类似,首先获取MappedStatement,然后调用了执行器的update方法。整体流程比较简单,有一点需要说明的是:对于数据库而言,insert/update/delete其实都是更新操作,所以这里虽然是insert方法入口,但是实际调用了Executor#update方法。

delete方法/update方法

delete/update方法的执行过程与insert基本是一样的,不再赘述。

小结

DefaultSqlSession是SqlSession的默认实现,提供了数据库的CURD能力,但是它并没有直接与数据库交互,而是调用了Executor的相关方法,是一个名副其实的“甩手掌柜”。

在selectXxx或insert方法中,我们并没有发现它调用close方法,所以它不具备自动关闭的能力,需要我们开发者手动调用,比如示例中的“try-with-resource”方式。

回过头来说下“线程安全”:通过上述示例和源码,我们会发现一旦DefaultSqlSession对象被创建,任何获取到它的线程都可以访问其方法,CURD或者close,多个线程同时操作就会导致不可预知的结果。所以,我们不能把它直接设置为公共实例,应该即用即取,及时释放。

SqlSessionManager

看完DefaultSqlSession,继续看下SqlSessionManager。由上述类图可知,它实现了SqlSession和SqlSessionFactory两个接口,同时具备SqlSession接口能力及SqlSession对象创建能力。

仔细观察类图会发现,SqlSessionManager内部还聚合了一个SqlSessionFactory和SqlSession对象,什么情况?实现了这两个接口,却又聚合了这两个接口的实例,难道它只是个代理,就是包了一层?源码走走看!

 1public class SqlSessionMgrDemo {
2    public static void main(String[] args) throws IOException {
3        // mybatis配置文件
4        String path = "mybatis-config.xml";
5        InputStream inputStream = Resources.getResourceAsStream(path);
6        // 获取 SqlSessionManager
7        SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream);
8        // 开启对session的管理:没有这行代码也可以使用
9        sqlSessionManager.startManagedSession();
10        // 获取mapper
11        CompanyDao dao = sqlSessionManager.getMapper(CompanyDao.class);
12        // 执行查询
13        CompanyDO companyDO = dao.selectById(1);
14        System.out.println(companyDO);
15    }
16}

简单说下这段代码的执行流程:

  • 4~5行:加载并读取配置文件;

  • 7行:创建SqlSessionManager实例;

  • 9行:开启对SqlSession的管理,通过它可以实现对SqlSession的线程安全管理,使SqlSessionManager实例可共享(这个与DefaultSqlSession有差别);

  • 11~14行:执行查询;

实例化

通过示例代码可知,SqlSessionManager#newInstance创建了SqlSessionManager对象,看下执行过程,我们仔细看看SqlSessionManager到底是个什么东东。

 1// 创建 SqlSessionManager
2public static SqlSessionManager newInstance(InputStream inputStream) {
3  return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null));
4}
5
6// 构造方法,这里入参为DefaultSqlSessionFactory实例
7private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
8
9  this.sqlSessionFactory = sqlSessionFactory;
10
11  // 通过动态代理,创建SqlSession实例
12  this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
13      SqlSessionFactory.class.getClassLoader(),
14      new Class[]{SqlSession.class},
15      new SqlSessionInterceptor());
16}
17
18// SqlSessionFactory实例
19private final SqlSessionFactory sqlSessionFactory;
20
21// SqlSession 代理对象
22private final SqlSession sqlSessionProxy;

newInstance方法调用了SqlSessionManager的私有构造方法(仅有私有构造方法),构造方法的入参是SqlSessionFactory(与DefaultSqlSession的实现一致),这里是其默认实现DefaultSqlSessionFactory。

构造方法内通过动态代理创建对象sqlSessionProxy,它是SqlSession的动态实现,这里需要重点关注SqlSessionInterceptor这个类。从名称看它是一个拦截器,也就是说当我们调用SqlSession的方法时,会被它拦截,看下SqlSessionInterceptor#invoke方法:

 1  private class SqlSessionInterceptor implements InvocationHandler {
2    public SqlSessionInterceptor() {
3        // Prevent Synthetic Access
4    }
5
6    @Override
7    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
8
9      // 判断本地线程是否有sqlSession
10      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
11      // 如果有,直接使用,无需创建
12      if (sqlSession != null) {
13        try {
14          // 调用SqlSession接口方法实现
15          return method.invoke(sqlSession, args);
16        } catch (Throwable t) {
17          throw ExceptionUtil.unwrapThrowable(t);
18        }
19      } else {
20        // 如果没有,通过openSession创建,openSession方法返回的是DefaultSqlSession实例
21        //这里使用try-with-resource,使用完成会自动关闭
22        try (SqlSession autoSqlSession = openSession()) {
23          try {
24            // 调用SqlSession接口方法实现
25            final Object result = method.invoke(autoSqlSession, args);
26            //事务提交
27            autoSqlSession.commit();
28            return result;
29          } catch (Throwable t) {
30            //事务回滚
31            autoSqlSession.rollback();
32            throw ExceptionUtil.unwrapThrowable(t);
33          }
34        }
35      }
36    }
37  }

SqlSessionInterceptor#invoke首先会从localSqlSession获取SqlSession对象,localSqlSession是什么呢?这里只提到get,那set在哪里?从代码里面找下吧:

 1  //localSqlSession是一个ThreadLocal对象,用来存储SqlSession
2  //这是一个线程安全的标志
3  private final ThreadLocal localSqlSession = new ThreadLocal<>(); 4 5  //开启对SqlSession的管理,我们示例代码有调用 6  public void startManagedSession() { 7    //为localSqlSession赋值,参数为openSession返回值 8    this.localSqlSession.set(openSession()); 9  }1011  public SqlSession openSession() {12    //与之前一样,它返回的是DefaultSqlSession对象13    return sqlSessionFactory.openSession();14  }

localSqlSession是一个ThreadLocal对象,用来存储SqlSession,它用来在线程内共享对象,很明显的线程安全处理方式。localSqlSession是在startManagedSession及其重载方法中设置的,通过openSession方法可知,localSqlSession内部存放的是DefaultSqlSession对象。
继续SqlSessionInterceptor#invoke分析,根据localSqlSession中是否存有sqlSession,有两个不同的处理方式:

  • 有:如果有,则直接使用,不再新建。这里只能是在同一线程内共享,避免了重复创建对象的额外开销。

  • 无:如果没有,就与我们DefaultSqlSession一节中的使用方式一样,使用了“try-with-resource”方式,实现了SqlSession的自动关闭。

SqlSessionManager持有的SqlSessionFactory引用为DefaultSqlSessionFactory,作用自然是创建SqlSession实例;SqlSessionFactory创建的SqlSession是DefaultSqlSession实例;但是SqlSessionManager内部通过动态代理模式使用SqlSessionInterceptor对DefaultSqlSession进行了拦截,在拦截处理中有两种逻辑:一是使用ThreadLocal对SqlSession进行了线程安全共享;二是实现了对SqlSession的自动关闭。由此可见:SqlSessionManager是DefaultSqlSession的加强版,或者说是一个高级封装。

SqlSession接口实现

SqlSessionManager对SqlSession接口的实现全部转交给其内部的sqlSessionProxy代理对象完成,没有什么处理逻辑,贴几个方法看下:

 1  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
2    sqlSessionProxy.select(statement, parameter, rowBounds, handler);
3  }
4
5  public int insert(String statement) {
6    return sqlSessionProxy.insert(statement);
7  }
8
9  public int update(String statement) {
10    return sqlSessionProxy.update(statement);
11  }
12
13  public int delete(String statement) {
14    return sqlSessionProxy.delete(statement);
15  }

sqlSessionProxy其实是一个包装了DefaultSqlSession的SqlSessionInterceptor实例,所有方法会被SqlSessionInterceptor#invoke拦截,最终是由DefaultSqlSession来实现的(就是前面一节说的内容)。

前面“实例化”一节说invoke内有两种逻辑,那实际会走哪种逻辑呢?实际上,这引出了SqlSessionManager的两种使用方式,使用方式不同invoke的执行逻辑也不同。

  • 一是:像使用DefaultSqlSession一样,按需创建,此时SqlSessionManager提供了自动关闭功能。

  • 二是:启用SqlSession管理功能。此时SqlSessionManager会在同一线程内共享SqlSession,它是线程安全的。

小伙伴儿们可以尝试把示例代码中第9行关闭或打开注释作下调试,一试便知。

小结

SqlSessionManager虽然同时实现了SqlSession和SqlSessionFactory两个接口,但是真正“干活”的还是DefaultSqlSession和DefaultSqlSessionFactory;它使用动态代理对SqlSession的方法进行拦截,提供了SqlSession自动关闭或线程安全的解决方案。整体上看,SqlSessionManager是对安全、智能、高级封装版的DefaultSqlSession。

SqlSession总结

本文“啰里八嗦”的介绍了DefaultSqlSession和SqlSessionManager两者的区别与联系,重点关注了SqlSessionManager如何解决线程安全和自动关闭两个问题,从源码层面了解到SqlSessionManager其实是对DefaultSqlSession的高级封装。

我们还了解到,所有的SQL命令都会从SqlSession开始,然而SqlSession并没有直接操作数据库,脏活累活都交给了Executor,形象点说SqlSession其实是一个“包工头儿”。具体Executor如何做,且听下文讲解。

感谢您的阅读,如有问题欢迎讨论!也可以关注我的微信公众号:“兮一昂吧”。欢迎点赞、在看、转发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/365569.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

r语言kmodes_聚类分析——k-means算法及R语言实现

我们知道『物以类聚&#xff0c;人以群分』&#xff0c;这里并不是分类问题&#xff0c;而是聚类问题。两者主要区别在于&#xff0c;分类是将一组数据根据不同的类区分&#xff0c;已经知道有哪些类&#xff0c;也就是数据已经有了类的标签。而聚类是一种事先不知道有多少类&a…

VSCode安装jshint插件报错

Mac电脑上使用VSCode安装jshint插件时提示如下错误&#xff1a; Failed to load jshint library. Please install jshint in your workspace folder using npm install jshint or globally using npm install -g jshint and then press Retry. 按照提示&#xff0c;使用np…

集合框架总结

2019作为新的一年开始&#xff0c;我也着手面试的准备。这篇的博客的主角集合--面试中都会出现的&#xff0c;所以今天特作此总结&#xff0c;也算是复习的成果的一个展示。在查看了许多的博客和源码后我决定将其分成3部分来总结。 三个部分分别是&#xff1a;集合的分类、各个…

调查内存泄漏第2部分–分析问题

这个小型系列的第一个博客介绍了如何创建一个非常泄漏的示例应用程序&#xff0c;以便我们可以研究解决服务器应用程序上基于堆的问题的技术。 它展示了Producer-Consumer模式的一个大问题&#xff0c;即消费者代码必须能够至少与生产者一样快&#xff08;如果不是更快&#xf…

调查内存泄漏第1部分–编写泄漏代码

前几天&#xff0c;我发现了这个小问题&#xff1a;该服务器运行了一段时间&#xff0c;然后掉下来了。 然后通过启动脚本重新启动&#xff0c;整个过程重复进行。 这听起来并不那么糟糕&#xff0c;尽管对数据的损失很大&#xff0c;但对业务的重要性并不重要&#xff0c;因此…

[NOIP2013]火柴排队

嘟嘟嘟 首先可以想到&#xff0c;最小距离一定是a中第 i 大的和b中第 i 大的在同一行。 然后先把a&#xff0c;b分别离散化&#xff0c;然后开一个标记数组&#xff0c;map[i]记录a中第 i 小的数在哪一个位置出现&#xff0c;然后对b数组处理一遍。 题中说交换次数&#xff0c;…

2018秋季C语言学习总结

转载于:https://www.cnblogs.com/noacgnnolife/p/10413255.html

解决Charles手机安装SSL证书后,获取到的接口为unknown,且乱码问题

按照正常流程将Charles安装并设置代理后&#xff0c;手机添加完代理并安装SSL证书&#xff0c;尝试抓取接口时&#xff0c;获取到的接口为unknown且返回内容乱码&#xff0c;如下图所示 解决办法&#xff1a; 在Proxy-SSL Proxying Settings-SSL Proxying下添加想要抓取的服务地…

Sum of Even Numbers After Queries

Solution: 转载于:https://www.cnblogs.com/Julietma/p/10414394.html

Python学习week7-文件操作

1、文件IO常用操作 # 文件操作命令 2、打开操作open # open(file, moder, buffering-1, encodingNone, errorsNone, newlineNone, closefdTrue, openerNone) 创建并打开一个文件test&#xff0c;然后关闭&#xff1b;打开一个文件&#xff0c;返回一个文件对象&#xff08;流对…

风险定量分析工具 龙卷风图 决策树形图 蒙特卡洛模拟

龙卷风图&#xff1a;是项目管理中用于在风险识别和定性分析之后&#xff0c;进行定量风险分析的技术----敏感性分析技术中最常用的一种图表技术。 敏感性分析&#xff1a;敏感性分析有助于确定哪些风险对项目具有最大的潜在影响。它把所有其他不确定因素保持在基准值的条件下…

推土机:将JAXB对象映射到业务/域对象

Dozer是开放源代码&#xff08; Apache 2许可 &#xff09;“ Java Bean到Java Bean映射器&#xff0c;可将数据从一个对象递归复制到另一个对象”。 正如从其主页上的描述所描述的那样&#xff0c;它用于映射两个JavaBeans实例&#xff0c;以在实例之间进行自动数据复制。 尽管…

openssl不是内部或外部命令_OpenSSL新架构蓝图

概述日前OpenSSL官网公布了未来OpenSSL的架构蓝图。作为战略性的架构目标&#xff0c;需要大量的版本迭代本文档概述了OpenSSL战略架构。它需要多个版本的迭代从目前最新的版本1.1开始直到3.0甚至是4.0最终实现。由于版本架构变动非常大&#xff0c;涉及大量的变化和迭代&#…

休眠事实:始终检查Criteria API SQL查询

Criteria API对于动态构建查询非常有用&#xff0c;但这是我使用它的唯一用例。 每当您有一个带有N个过滤器且可以以任意M个组合到达的UI时&#xff0c;都有一个API动态构造查询是有意义的&#xff0c;因为串联字符串始终是我所不愿使用的路径。 问题是&#xff0c;您是否知道…

treegrid,可以展开的jqgrid树

效果图 html部分 <div class"padding20 bgWhite marginTop20"> <div class"cus-grid row" id"grid-wrap"> <div class"col-lg-12"> <table id"list2"></table> …

winfrom软件开发汽车测试_ETci — 全自动软件测试调度(持续集成)平台

ETci 提供了编译- 测试- 发布解决方案&#xff0c;包括&#xff1a;自动提取配置库代码进行自动构建, 自动调度静态测试工具(如QAC)进行静态测试&#xff0c;自动调度单元测试工具(如Tessy)开展动态测试&#xff0c;自动调度HIL 自动化测试系统等。使得开发、测试团队在软件开发…

在POJO中使用ThreadLocal的Java嵌套事务

大多数嵌套事务是使用EJB实现的&#xff0c;现在我们尝试在POJO上实现嵌套事务。 在这里&#xff0c;我们使用了ThreadLocal的功能。 了解嵌套事务 事务可以嵌套在另一个内部。 因此&#xff0c;内部事务或外部事务可以回滚或提交&#xff0c;而不会影响其他事务。 创建新事务…

HTML存储详解

和大家一起先来了解一下H5之前的存储方式&#xff1a; cookies的诞生&#xff1a; http请求头上带着数据大小只能为4K主Domain的污染 下面是百度的一些Cookies HTTP中带√的表示&#xff0c;只能被服务器端修改的数据&#xff0c;一般用来存储身份验证等信息 cookies造成了…

springboot 工程启动报错之Consider defining a bean of type ‘XXX’ in your configuration.

一、前言&#xff1a; 使用springboot自动注入的方式搭建好了工程&#xff0c;结果启动的时候报错了&#xff01;&#xff01;&#xff01;&#xff0c;错误如下图&#xff1a; Description:Field userEntityMapper in com.xxx.xxx.service.UserService required a bean of typ…

java 自定义报表_灵活数据分析 | 自定义数据分析_集力数据系统平台_Java报表系统软件...

灵活数据分析集力数据系统数据分析是立足于让终端用户即使不懂专业计算机技术也能即时定义报表和分析数据的工具。用户只需关心业务需要&#xff0c;无需关心技术实现&#xff0c;通过拖拖拽拽、点点选选即可轻松制作列表式报表、分组报表、交叉报表、自由报表、组合报表等并进…