Mybatis 中sqlsession源码解析

一、sqlsession获取过程

1、基础配置

  在mybatis框架下进行的数据库操作都需要首先获取sqlsession,在mybatis与spring集成后获取sqlsession需要用到sqlsessionTemplate这个类。

首先在spring对sqlsessionTemplate进行配置,使用到的是 org.mybatis.spring.SqlSessionTemplate 这个类。

<!-- SqlSession实例 -->
<bean id="sessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"
        destroy-method="close">
<!--当构造函数有多个参数时,可以使用constructor-arg标签的index属性,index属性的值从0开始,这里将sqlsessionFactory作为第一个参数传入-->
<constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
<!-- 将数据源映射到sqlSessionFactory中 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
    <property name="dataSource" ref="dataSource" />
</bean>

所以在sqlsessionTemplate的初始化过程中,首先会将sqlsessionFactory作为参数传入,sqlsessionFactory中映射了数据源信息。

配置事务,在后来的sqlsession获取过程中会对事务进行判断

<!--======= 事务配置 Begin ================= -->
<!-- 事务管理器(由Spring管理MyBatis的事务) -->
<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 关联数据源 -->
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!--======= 事务配置 End =================== -->

2、sqlsessionTemplate的初始化

public class SqlSessionTemplate implements SqlSession {
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property ‘sqlSessionFactory‘ is required");
        Assert.notNull(executorType, "Property ‘executorType‘ is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }

  SqlsessionTemplate类的最开始初始化过程中,首先会通过sqlsessionFactory参数进行构造,通过Proxy.newProxyInstance()方法来创建代理类,表示创建SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlsessionTemplate的一个内部类SqlSessionInterceptor的invoke方法,最终初始化sqlsessionProxy。

3、sqlsession的调用过程

由于上面两个过程中已经将sqlsessionTemplate中的sqlsessionProxy已经初始化完毕,所以在代码中可以进行调用。调用最终都会进入SqlSessionInterceptor的invoke方法。

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的)
      //这个方法可以根据Spring的事物上下文来获取事物范围内的sqlSession
      final SqlSession sqlSession = SqlSessionUtils.getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //调用真实SqlSession的方法
        Object result = method.invoke(sqlSession, args);
        //然后判断一下当前的sqlSession是否有配置Spring事务 如果没有自动commit
        if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        //返回执行结果
        return result;
      } catch (Throwable t) {
        //如果出现异常则根据情况转换后抛出
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        //关闭sqlSession
        //它会根据当前的sqlSession是否在Spring的事务上下文当中来执行具体的关闭动作
        //如果sqlSession被Spring事务管理 则调用holder.released(); 使计数器-1
        //否则才真正的关闭sqlSession
        SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }

在上面的代码中用到两个很关键的方法:

  获取sqlsession方法:SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

  关闭sqlsession方法:SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    //根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,当sqlSessionFactory创建了sqlSession,就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
 //如果holder不为空,且和当前事务同步
    if (holder != null && holder.isSynchronizedWithTransaction()) {
      //hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用
      if (holder.getExecutorType() != executorType) {
        throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
      }
      //增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加
      holder.requested();
   //返回sqlSession
      return holder.getSqlSession();
    }
 //如果找不到,则根据执行类型构造一个新的sqlSession
    SqlSession session = sessionFactory.openSession(executorType);
 //判断同步是否激活,只要SpringTX被激活,就是true
    if (isSynchronizationActive()) {
   //加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务
      Environment environment = sessionFactory.getConfiguration().getEnvironment();
      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
  //如果是,则将sqlSession加载进事务管理的本地线程缓存中
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
  //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中
        bindResource(sessionFactory, holder);
  //将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations
        registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        //设置当前holder和当前事务同步
  holder.setSynchronizedWithTransaction(true);
  //增加引用数
        holder.requested();
      } else {
        if (getResource(environment.getDataSource()) == null) {
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
    }
    return session;
  }
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
 //其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
   //这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用
      holder.released();
    } else {
   //如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close
      session.close();
    }
  }

二、mybatis的缓存

1、一级缓存

  Mybatis的一级缓存是默认开启的,主要是通过sqlsession来实现的,每个sqlsession对象会在本地创建一个缓存(local cache),对于每次查询都会在本地缓存中进行查询,如果命中则直接返回,如果没查到则进入到数据库中进行查找。一级缓存是sqlsession级别的。

  从上面sqlsession的获取源码中可以看到,每次获取一个全新的sqlsession最终都是会保存在ThreadLocal中跟线程绑定,如果在spring中配置了事务则整个事务周期里面都共享一个sqlsession,如果没有配置事务则每次请求都是一个独立的sqlsession。每次执行完后数据库操作后,如果还在事务周期中只对sqlsession的引用次数减一,否则直接关闭sqlsession。

一级缓存执行的时序图:

小结:

MyBatis一级缓存的生命周期和SqlSession一致。
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

2、二级缓存

在系统中如果需要使用二级缓存则直接在spring中进行配置声明即可。

<!-- 这个配置使全局的映射器启用或禁用 缓存 -->
<setting name="cacheEnabled" value="true" />
<!-- 开启二级缓存开关 -->
<cache/>

  MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis,Memcached等分布式缓存可能成本更低,安全性也更高。

原文地址:https://www.cnblogs.com/kma-3/p/9957740.html

时间: 2024-10-08 11:18:26

Mybatis 中sqlsession源码解析的相关文章

Mybatis中selectKey源码分析

刚回答了一个问题这样一个问题,mybatis不能正常返回主键增加值  下面通过源码分析一下selectKey都具体实现:关于Mybatis 基于注解Mapper源码分析 可以看一下具体解析过程. @Insert("insert into table2 (name) values(#{name})") @SelectKey(statement="call identity()", keyProperty="nameId", before=false

DeepLearnToolBox中CNN源码解析

DeepLearnToolbox是一个简单理解CNN过程的工具箱,可以在github下载.为了理解卷积神经网络的过程,我特此对CNN部分源码进行了注释.公式的计算可以由上一篇blog推导得出. 注意:代码中没有的subsampling进行设置参数,将subsampling层的参数w就设置为了0.25,而偏置参数b设置为0.卷积层计算过程为上一层所有feature map的卷积的结果和,后再加一个偏置,再取sigmoid函数.而subsampling的计算过程为上一层对应的2*2的feature

MatConvNet中mnist源码解析

本文的代码来自MatConvNet 下面是自己对代码的注释: cnn_mnist_init.m function net = cnn_mnist_init(varargin) % CNN_MNIST_LENET Initialize a CNN similar for MNIST opts.useBatchNorm = true ; #batchNorm是否使用 opts.networkType = 'simplenn' ; #网络结构使用lenet结构 opts = vl_argparse(o

mybatis通用mapper源码解析(二)

1.javabean的属性值生成sql /** * 获取所有查询列,如id,name,code... * * @param entityClass * @return */ public static String getAllColumns(Class<?> entityClass) { Set<EntityColumn> columnSet = EntityHelper.getColumns(entityClass); StringBuilder sql = new Strin

Android 热修复Nuwa的原理及Gradle插件源码解析

现在,热修复的具体实现方案开源的也有很多,原理也大同小异,本篇文章以Nuwa为例,深入剖析. Nuwa的github地址 https://github.com/jasonross/Nuwa 以及用于hotpatch生成的gradle插件地址 https://github.com/jasonross/NuwaGradle 而Nuwa的具体实现是根据QQ空间的热修复方案来实现的.安卓App热补丁动态修复技术介绍.在阅读本篇文章之前,请先阅读该文章. 从QQ空间终端开发团队的文章中可以总结出要进行热更

Spring 源码解析之DispatcherServlet源码解析(五)

Spring 源码解析之DispatcherServlet源码解析(五) 前言 本文需要有前四篇文章的基础,才能够清晰易懂,有兴趣可以先看看详细的流程,这篇文章可以说是第一篇文章,也可以说是前四篇文章的的汇总,Spring的整个请求流程都是围绕着DispatcherServlet进行的 类结构图 根据类的结构来说DispatcherServlet本身也是继承了HttpServlet的,所有的请求都是根据这一个Servlet来进行转发的,同时解释了为什么需要在web.xml进行如下配置,因为Spr

设计模式课程 设计模式精讲 6-3 抽象工厂源码解析

1 源码解析 1.1 mysql源码解析 1.2 mybaties 的sqlsession源码解析 1 源码解析 1.1 mysql源码解析 1.2 mybaties 的sqlsession源码解析 原文地址:https://www.cnblogs.com/1446358788-qq/p/11295158.html

Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? ??如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一样,本篇文章最最核心的要点就是 SqlSession实现数据库操作的源码解析.但按照惯例,我这边依然列出如下的问题: 1. SqlSession 是如何被创建的? 每次的数据库操作都会创建一个新的SqlSession么?(也许有很多同学会说SqlSession是通过 SqlSessionFactor

Mybatis源码解析(一)(2015年06月11日)

一.简介 先看看Mybatis的源码结构图,Mybatis3.2.7版本包含的包共计19个,其他版本可能会少. 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得,而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例.