2.8、mybatis源码分析之创建SqlSession流程

一、mybatis接口层

  • 在讲创建SqlSession之前,先来介绍下myabtis中的接口层API


    SqlSession相关类结构

1、SqlSession是mybatis的核心接口之一,是myabtis接口层的主要组成部分,对外提供了mybatis常用的api。myabtis提供了两个SqlSesion接口的实现,常用的实现类是DefaultSqlSession。
2、SqlSessionFactory负责创建SqlSession,其中包含多个openSession方法的重载,具体创建在DefaultSqlSessionFactory实现类中。

1、DefaultSqlSession

  • SqlSession接口的主要实现类,在DefaultSqlSession中使用了策略模式,把数据库相关的操作全部封装到了Executor接口实现中,并通过executor字段选择不同的Executor实现类,最终操作由Executor接口实现类完成。
public class DefaultSqlSession implements SqlSession {
//configuration配置对象
  private final Configuration configuration;
//数据库操作相关的executor接口(策略模式)
  private final Executor executor;
//是否自动提交
  private final boolean autoCommit;
//当前缓存中是否有脏数据
  private boolean dirty;
//为防止用户忘记关闭打开的游标对象,会通过cursorList字段记录由该sqlsession对象生成的游标对象
  private List<Cursor<?>> cursorList;

2、DefaultSqlSessionFactory

  • 是SqlSessionFactory接口的具体实现类,该类主要提供了两种方式创建相应的DefaultSqlSession对象。一种是通过数据源获取数据库连接,并创建Executor对象以及DefaultSqlSession对象
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
//获取配置的environment对象
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据配置创建Executor 对象
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  • 另一种是用户提供数据库连接对象,DefaultSqlSessionFactory会使用该数据库连接对象创建相应的Executor对象以及DefaultSqlSession对象
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }      
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

二、Executor接口

  • mybatis的核心接口之一,其中定义了数据库操作的基本方法。实际应用中经常涉及的sqlSession接口的功能,都是基于Executor接口实现的。
  • mybatis中提供了Executor的接口实现,这些接口实现中涉及两种涉及模式,分别是模板方法模式和装饰器模式。其中CachingEecutor扮演了装饰器的角色,为Executor添加了二级缓存的功能。


    Executor接口实现类结构

1、BaseExecutor

  • Executor接口的抽象实现类,实现了Executor接口的发部分方法,其中就是使用了模板方法模式。在BaseExecutor中主要提供了缓存管理(比如一级固定模式不变的一级缓存)和事物管理的基本功能,继承BaseExecutor的子类只要实现四个基本方法来完成数据库的相关操作既可以,四个方法是doUpdate、doQuery、doQueryCursor、doFlushStatement方法。
public abstract class BaseExecutor implements Executor {
  private static final Log log = LogFactory.getLog(BaseExecutor.class);
//实现事务的提交和回滚和关闭操作
  protected Transaction transaction;
//封装executor对象
  protected Executor wrapper;
//延迟队列
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
//一级缓存用于该Executor对象查询结果集映射得到的结果对象
  protected PerpetualCache localCache;
//一级缓存用于缓存输出类型的参数
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;
  protected int queryStack;
  private boolean closed;
其他方法

2、SimpleExecutor

  • 继承自BaseExecutor抽象类,主要专注的实现上面介绍的四个类。例如doQuery查询方法:
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
//获取配置文件
      Configuration configuration = ms.getConfiguration();
//根据相应参数创建具体额StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
//执行StatementHandler.query方法执行sql语句并通过ResultSetHandler完成结果集映射。
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
//创建Statement对象
    stmt = handler.prepare(connection, transaction.getTimeout());
//处理占位符号
    handler.parameterize(stmt);
    return stmt;
  }

2、ReuseExecutor

  • ReuseExecutor提供了Statement重用的功能,ReuseExecutor中通过statementMap字段缓存使用过的Statement对象,key是sql语句,value是对应的Statement对象。
  • 和SimpleExecutor的区别是在prepareStatement方法,会尝试重用StatementMap中的缓存的Statement对象。
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
//获取sql语句
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
//修改超时时间
      applyTransactionTimeout(stmt);
    } else {
//获取数据库连接并创建Statement并缓存发哦StatementMap中
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }
  private Statement getStatement(String s) {
    return statementMap.get(s);
  }
 private boolean hasStatementFor(String sql) {
    try {
      return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
    } catch (SQLException e) {
      return false;
    }
  }
  private void putStatement(String sql, Statement stmt) {
    statementMap.put(sql, stmt);
  }

4、BatchExecutor

  • BatchExexutor提供了批量操作sql语句的功能,也就是在可以在客户端缓存多条sql语句,并在何时的时机将多条sql语句打包发送给数据库执行,从而减少网络方面的开销。
public class BatchExecutor extends BaseExecutor {
    //缓存多个statement对象,其中每一个statement对象中都缓存可许多条sql语句
  private final List<Statement> statementList = new ArrayList<Statement>();
  //记录批处理的结果
  private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
  //当前的sql语句
  private String currentSql;
  //当前的MappedStatement对象
  private MappedStatement currentStatement;

  public BatchExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    //如果当前执行的sql模式与上次执行的sql模式相同且对应的MappedStatement对象相同
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      //参数绑定处理?占位符号
      applyTransactionTimeout(stmt);
     handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
  // 底层使用statement.addBatch方法添加sql语句
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

5、CacheExecutor

  • 是Executor接口的装饰器,为Executor对象增加二级缓存的相关功能。
  • 具体实现讲缓存部分在说

三、创建SqlSession流程

  • 前面介绍了关于myabtis接口层的东西,下面我们步入正题,研究创建SqlSession的流程。
  • 入口:SqlSession sqlSession = sqlSessionFactory.openSession();

1、DefaultSqlSessionFactory的openSession()方法

  • 最终调用openSessionFromDataSource方法,前面已经讲过
public SqlSession openSession() {
  /*
  configuration.getDefaultExecutorType()是获取ExecutorType类型,在Config配置文件中可以配置
   有三种形式SIMPLE(普通的执行器;), REUSE(执行器会重用预处理语句), BATCH(执行器将重用语句并执行批量更新),
默认的是SIMPLE
  */
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

2、Configuration的newExecutor方法创建Executor对象

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //根据executorType来创建相应的执行器
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
      //如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
    /**
      * 二级缓存开关配置示例
     * <settings>
     *   <setting name="cacheEnabled" value="true"/>
     * </settings>
     */
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //此处调用插件,通过插件可以改变Executor行为
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

3、new DefaultSqlSession创建SqlSession对象

  public DefaultSqlSession(Configuration configuration, Executor 
executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

四、创建SqlSession流程图

创建SqlSession流程图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容

  • 前言 主题是Mybatis一级和二级缓存的应用及源码分析。希望在本场chat结束后,能够帮助读者朋友明白以下三点。...
    余平的余_余平的平阅读 1,327评论 0 12
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,510评论 0 4
  • 第一章 天宫,诛仙台 一个素衣女子站在诛仙台之上,狂风吹的他衣袍猎猎作响,发丝飞舞,说着他拿出一面铜镜,顿时,铜镜...
    墨渊的小十七阅读 494评论 0 1
  • 前阵子跟追风是的把所有的项目都做完了,结果现在就闲的跟什么是的,整天没事干。无聊中就思考自己,反思自己的不足和需要...
    LuxDark阅读 302评论 0 1
  • 前几天看到一位台湾的心理学家讲,负面情绪也能救人性命。 我初还不以为然,因为诸如愤怒、恐惧、焦虑、悲伤等等一些负面...
    瞎驴阅读 228评论 0 0