文档章节

MyBatis源码学习系列:02-核心接口SqlSessionFactory和SqlSession

Yalong
 Yalong
发布于 2017/08/09 17:08
字数 2632
阅读 18
收藏 0

要操作数据,我们需要先看一下两个核心接口:SqlSessionFactory和SqlSession。
在初始化的时候我们初始化了一个DefaultSqlSessionFactory的实例,它就实现了SqlSessionFactory接口,下面是SqlSessionFactory的接口代码:

public interface SqlSessionFactory {
	
  //打开一个SqlSession
  SqlSession openSession();
  //打开SqlSession,并指定是否自动提交
  SqlSession openSession(boolean autoCommit);
  //打开SqlSession,并指定数据库连接对象
  SqlSession openSession(Connection connection);
  //打开SqlSession,并指定事务隔离级别
  SqlSession openSession(TransactionIsolationLevel level);
  
  //下面四个和上面四个一样,但可以指定执行器类型,MyBatis提供了SIMPLE,REUSE,BATCH三种执行器。
  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);
  Configuration getConfiguration();
}

此接口很简单,除了一个返回当前的配置信息也就是Configuration对象外,还提供openSession及其重载方法。可以指定是否自动提交,数据库连接,事务隔离级别以及MyBatis执行器类型等信息。事务隔离级别后面单独介绍。执行器类型后面介绍执行器的时候介绍,此工厂接口主要用来获取SqlSession实例,进而操作数据库。

SqlSessionFactory接口默认有两个实现类:

首先看下DefaultSqlSessionFactory。

此类实现的一系列openSession方法及其重载方法在方法内部都调用了openSessionFromDataSource和openSessionFromConnection两个私有方法。    

  //根据执行器类型信息以及指定的JDBC连接对象创建SqlSession
  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);
      
      //根据事务工厂类创建一个SqlSession的事务对象
      final Transaction tx = transactionFactory.newTransaction(connection);
      
      //根据事务对象以及执行器类型创建执行器对象
      final Executor executor = configuration.newExecutor(tx, execType);
      
      //创建默认的SqlSession实例对象
      return new DefaultSqlSession(configuration, executor, autoCommit);
      
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  //根据执行器类型信息,事务隔离级别配置以及是否自动提交创建SqlSession
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //从配置信息中获取当前使用的环境配置信息
      final Environment environment = configuration.getEnvironment();
      //根据当前使用的环境配置信息创建事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //使用当前配置的DataSource信息创建事务对象
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //创建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建默认的SqlSession实例对象。
      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();
    }
  }

上面两个方法分别从指定的Connection对象或者配置文件中的DataSource信息中创建一个DefaultSqlSession类的对象。可以看出来。DefaultSqlSessionFactory这个默认的工厂类在内部实际上还是根据传入方法的参数,来决定是如何创建一个SqlSession对象。

接下来看一下SqlSessionFactory接口的另一个实现类SqlSessionManager:

SqlSessionManager这个类除了实现了SqlSessionFactory接口外,还实现了SqlSession接口,这里我们先关注红框的部分。

这个类的主要作用是可以不用先创建一个工厂类,可以直接根据配置信息来获取SqlSession对象。下面看代码:

 //内部持有一个默认工厂类对象
  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
  
  //构造方法私有化,必须通过newInstance方法创建
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
	//设置SqlSession工厂对象
    this.sqlSessionFactory = sqlSessionFactory;
    //创建SqlSession代理类
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
  //使用Reader字符流传递配置信息来构造SqlSessionManager类
  public static SqlSessionManager newInstance(Reader reader) {
	//这里和之前的过程类似,还是使用SqlSessionFactoryBuilder类来加载配置信息,
	//然后build出一个SqlSessionFactory工厂类的实例,最后创建manager的对象
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
  }

虽然我们不用显式的先创建一个工厂类,直接使用SqlSessionManager获取SqlSession。但SqlSessionManager在内部会自动创建一个DefaultSqlSessionFactory实例。然后直接通过SqlSessionFactory的接口的实现方法中调用内部的工厂类去创建一个SqlSession对象。

 

此类除了实现了Factory接口从而可以创建新的SqlSession对象外,还实现了SqlSession接口。意思就是说可以直接通过SqlSessionManager类来执行具体的数据库操作。

 

当使用SqlSessionManager类来操作SqlSession接口方法时,并不是直接每次创建一个SqlSession对象去操作。而是使用了ThreadLocal类来保存当前线程中的SqlSession对象。下面看代码:

  //内部持有一个默认工厂类对象
  private final SqlSessionFactory sqlSessionFactory;
  
  //SqlSession代理类,通过SqlSession接口方法操作数据库时使用的时此代理类。
  private final SqlSession sqlSessionProxy;
  //保存了当前线程的SqlSession信息
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
  
  //构造方法私有化,必须通过newInstance方法创建
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
	//设置SqlSession工厂对象
    this.sqlSessionFactory = sqlSessionFactory;
    //创建SqlSession代理类
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

由上面代码可见,在实例化SqlSessionManager的时候,已经为我们创建好了一个SqlSession的代理类。但是这个代理类在创建的时候,没有任何当前配置环境的人任何信息,也就是说,其代理类对象只是一个实现了SqlSession接口的普通对象。并不能去和数据库进行交互。那么还可以从哪里拿到真正的SqlSession对象呢?SqlSessionManager类中提供了一组startManagedSession及其重载方法,在方法内部会调用openSession来创建一个SqlSession对象,并保存到当前线程环境中。

  public void startManagedSession() {
    this.localSqlSession.set(openSession());
  }
  public void startManagedSession(boolean autoCommit) {
    this.localSqlSession.set(openSession(autoCommit));
  }

也就是说,如果需要用管理类来获取SqlSession对象从而操作数据库的话,必须先调用startManagedSession。然后就可以直接调用SqlSessionManager类的SqlSession接口方法了。例如以下方法。

这些方法内部都调用了localSqlSession中保存的SqlSession对象信息,看下面代码:

  @Override
  public void commit(boolean force) {
    //获取当前线程环境中的SqlSession对象
    final SqlSession sqlSession = localSqlSession.get();
    if (sqlSession == null) {//如果为空则提示必须调用startManagedSession方法来进行创建
      throw new SqlSessionException("Error:  Cannot commit.  No managed session is started.");
    }
    sqlSession.commit(force);
  }
  @Override
  public void rollback() {
    final SqlSession sqlSession = localSqlSession.get();
    if (sqlSession == null) {
      throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");
    }
    sqlSession.rollback();
  }

除了上面介绍的几个方法外,还有一些SqlSession接口的方法并不是直接从localSqlSession中获取对象后直接进行调用。而是使用的代理类来执行具体方法。看到此处有点迷惑,我们知道,在初始化的时候,该代理类并不能直接使用,那么为什么这里会直接使用代理类来实现呢?我们继续看。在创建代理类的时候,传入了一个InvocationHandler接口的实现类SqlSessionInterceptor。我们知道,在使用JDK动态代理时,必须传入一个InvocationHandler接口的实现。所有调用代理类的方法,最终都会调用该InvocationHandler接口实现类的invoke方法。因此可以在invoke方法中进行逻辑处理,那我们看下SqlSessionInterceptor时如何做的:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //虽然在SqlSession接口方法调用的时候没有从localSession中获取SqlSession实例。
      //但在代理类内部的方法拦截器中依然进行了调用。所以我们刚才的迷惑为什么会使用一个不能用的代理类来操作数据库的原因就清楚了
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      
     
      if (sqlSession != null) {
    	//localSqlSession中存在SqlSession对象则直接调用期对应方法。
        try {
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
    	//localSqlSession中不存在SqlSession对象,
    	//则默认新打开一个SqlSession。然后调用其对应方法,并进行commit。
        final SqlSession autoSqlSession = openSession();
        try {
          final Object result = method.invoke(autoSqlSession, args);
          autoSqlSession.commit();
          return result;
        } catch (Throwable t) {
          autoSqlSession.rollback();
          throw ExceptionUtil.unwrapThrowable(t);
        } finally {
          autoSqlSession.close();
        }
      }
    }

从代码上看,在拦截器内部依然会从localSqlSession中获取SqlSession对象,如果未获取到,则会重新生成一个SqlSession对象。也就是说,在调用SqlSessionManager对象的commit,getConnection等方法时,必须显式的调用startManagedSession来创建一个当前线程环境的SqlSession对象。而调用insert,update等方法时则可以直接调用,不用先初始化。

SqlSession接口:

此接口时MyBatis与数据库交互的直接核心接口。可以用来进行增删改查等操作。下面先看下SqlSession的方法信息和类继承信息。

 SqlSession接口的实现类:

实际上这两个实现类我们在之前的分析中都遇到过了。其中对SqlSessionManager还做了详细分析。而DefaultSqlSession类我们知道在SqlSessionFactory类生成SqlSession的时候,就是创建了DefaultSqlSession的实例。而DefaultSqlSession内部也是通过工厂方法创建的。也就是说目前所有的SqlSession调用都直接或间接的使用DefaultSqlSession实例进行数据库操作。

先看下DefaultSqlSession的部分源码:

public class DefaultSqlSession implements SqlSession {
	
  //持有当前配置信息
  private final Configuration configuration;
  
  //具体执行数据库操作的执行器
  private final Executor executor;
  
  //是否自动提交
  private final boolean autoCommit;
  
  //脏数据标志:已插入或更新的数据但未提交时标记为true,提交后或关闭后标记为false,在提交或回滚时判断是否需要执行提交或回滚
  private boolean dirty;
  
  
  private List<Cursor<?>> cursorList;
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

上面主要是DefaultSqlSession的部分源码,主要介绍了其属性和构造器。

其中需要重点介绍的是Executor类的对象executor。这个类是SqlSession接口对象真正和数据库交互的内部对象。而SqlSession接口是提供给外部用户使用的。这个对象放到后面介绍,暂时知道这个对象时做什么用的就行。

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //从配置信息中获取已经映射的语句
      MappedStatement ms = configuration.getMappedStatement(statement);
      
      //使用执行器执行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

上面是DefaultSqlSession中查询列表的方法。可以看出最终使用executor来执行具体的查询任务。

未完....

© 著作权归作者所有

共有 人打赏支持
Yalong
粉丝 1
博文 5
码字总数 6148
作品 0
西安
私信 提问
【深入浅出MyBatis系列十】与Spring集成

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/25
1K
2
【深入浅出MyBatis系列一】MyBatis入门

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/22
4.1K
0
mybatis学习总结-mybatis初体验

从开始工作到现在,用到的ORM框架都是Hibernate,听说过ibatis,但是一直为看过具体的用法。 前段时间面试的时候,发现好几个公司都问会不会ibatis,所以最近几天对其进行了一下学习,然后在...

qq58edf1d989a2d
2018/06/26
0
0
关于MyBatis sqlSession的一点整理

原文地址:关于MyBatis sqlSession的一点整理 工作中,需要学习一下MyBatis sqlSession的产生过程,翻看了mybatis-spring的源码,阅读了一些mybatis的相关doc,对mybatis sqlSession有了一些...

Realfighter
2015/01/04
0
1
ORM框架 hibernate和mybatis 连接数据详解

工作中,需要学习一下MyBatis sqlSession的产生过程,翻看了mybatis-spring的源码,阅读了一些mybatis的相关doc,对mybatis sqlSession有了一些认知和理解,这里简单的总结和整理一下。 首先...

LYQ1990
2016/04/28
37
0

没有更多内容

加载失败,请刷新页面

加载更多

matlab-线性代数 简单方程组求根(有唯一解) 非齐次线性方程组:常数项不全为零

  matlab : R2018a 64bit     OS : Windows 10 x64 typesetting : Markdown    blog : my.oschina.net/zhichengjiu    gitee : gitee.com/zhichengjiu   code clearclc% 2x+......

志成就
31分钟前
2
0
Ubuntu 时间同步配置备忘

缘起 目前使用的 Ubuntu 18 下经常出现时间错误,查了下是默认读取 NTP 服务器的时候出现了 timeout,几次手工修改后一重启就故态复萌了,至于这个问题应该是怪机房还是 GFW,就不清楚了。 ...

郁也风
52分钟前
2
0
计算最佳线程数

计算出应该用于应用程序的理论最佳线程数有助于我们的程序的性能,应用程序运行时特征主要有CPU密集型工作和主要等待IO两种特征,或者是混合一起。 CPU 任务 threads = number of CPUs + 1 在...

woshixin
今天
3
0
搜索引擎(Solr-索引详解2)

学习目标 1.掌握SolrJ的使用。 2.掌握索引API 3.掌握结构化数据导入DIH SolrJ介绍 SolrJ是什么? Solr提供的用于JAVA应用中访问solr服务API的客户端jar。在我们的应用中引入solrj: <depende...

这很耳东先生
今天
3
0
待整理完--分享如何一个月在阿里云账户多了700元

服务器领券地址

吴伟祥
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部