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

要操作数据,我们需要先看一下两个核心接口: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接口默认有两个实现类:
165431_hZAv_1272088.png

首先看下DefaultSqlSessionFactory。

165520_alFu_1272088.png

此类实现的一系列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:

170007_dWr0_1272088.png

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接口方法了。例如以下方法。

170148_6hKz_1272088.png

这些方法内部都调用了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的方法信息和类继承信息。

170802_81wd_1272088.png

 SqlSession接口的实现类:

170815_oA2G_1272088.png

实际上这两个实现类我们在之前的分析中都遇到过了。其中对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来执行具体的查询任务。

未完....

转载于:https://my.oschina.net/u/1272088/blog/1505384

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值