Mybatis[1] sqlSession

155 阅读5分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

SqlSession

根据我们的分析,实际上sqlSession承担了上层我们和数据库直接交互的这一工作。

  • 涉及设计模式:

    • builder(SqlSessionFactoryBuilder)
    • 代理(proxy)
    • 工厂模式(SqlSessionFactory)

0.接口分析

sqlSession中,mybatis团队对于这个类的注释是:

The primary Java interface for working with MyBatis.
Through this interface you can execute commands, get mappers and manage transactions.

跟我们对于这个类的性质描述是一样。同时,这个类也继承了Closeable,这侧面说明了SqlSession实际上和连接是建立了部分关系。

  • 实际上,sqlSession的close方法的实现如下:

    public void close() {
    try {
     executor.close(isCommitOrRollbackRequired(false));
     closeCursors();
     dirty = false;
    } finally {
     ErrorContext.instance().reset();
    }
    }
    

    也就是说,executor是数据库链接在mybatis这个组件中最底层的封装,sqlSession是聚合这个对象,提供给上层方便调用的接口的。

1.实现分析

Mybatis原生的实现类一共有3个(其实可以说是一个加两个半个):

  • DefaultSqlSession
  • SqlSessionManager
  • SqlSessionTemplate

为什么说是两个半?因为其实sqlSessionManager是一个聚合分发类,内部相关的接口实现是通过内部的sqlSessionProxy对象实现的;sqlSessionTemplate同样内置了SqlSession和SqlSessionFactory。

我们先来看看DefaultSqlSession的实现。

DefaultSqlSession

内部属性如下:

private final Configuration configuration;
private final Executor executor;
​
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;

在之前的流程探索中我们已经知道这个类的查询功能是通过executor实现的了,在这里就不考虑select相关的内容。

  • 这里有一个小细节:delete,insert,update,实际上最后调用的都是executor.update方法。

这里有两个变量:

  • dirty和autoCommit

在做update操作得时候dirty会变为true,在commit、rollback、close的时候,会变回false,这也意味着这个dirty实际上的意思就是当前的变更是否已经提交到数据库,和数据库保持一致了。

而autoCommit是构造方法中指定了的。

这两个变量的使用只有在调用指定是否强制commit的时候才会调用。

这里有一个Cursor的list,cursor实际上是在调用executor的queryCursor时写入的。

SqlSessionTemplate

其实sqlSessionTemplate是mybatis适配sping的包下的,因此SqlSessionTemplate相对来说可以说是专供spring使用的,内部一共有以下属性:

private final SqlSessionFactory sqlSessionFactory;
​
private final ExecutorType executorType;
​
private final SqlSession sqlSessionProxy;
​
private final PersistenceExceptionTranslator exceptionTranslator;

sqlSessionTemplate中的sqlSession,实际上是在构造方法中就已经指定了的代理类:

this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class }, new SqlSessionInterceptor());

对应的代理类如下:

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      Object result = method.invoke(sqlSession, args);
      if (!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) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator
            .translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

在这里再往下查看 getSqlSession,能看到 TransactionSynchronizationManager这个类,注意到这个类里会保存一些sqlSession实例。

在这里,sqlSession获取后执行,最后都会关闭【1】。

这里能看到mybatis中的sqlSession一些特性,实际上在接入spring后被去除了:

  • 我们需要注意这里sqlSession是强制commit的:

    if (!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);
    }
    

这里的关闭,对应的代码如下:

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
​
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
 LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
 holder.released();
} else {
 LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
 session.close();
}
}

这里出现了一个SqlSessionHolder,是用来在TransactionSynchronizationManager保存sqlSession的,并且同时实现了ResourceHolderSupport(在springframework.transaction.support包下),接入Spring的事务框架。

TransactionSynchronizationManager

TransactionSynchronizationManager类在SqlSessionTemplate类中经常出现,事实上的作用是作为一个线程级别的事务缓存。 看一下内部的属性就能清楚这一点:

private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");
​
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<>("Transaction synchronizations");
​
    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<>("Current transaction name");
​
    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<>("Current transaction read-only status");
​
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<>("Current transaction isolation level");
​
    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<>("Actual transaction active");

到这里可以意识到,mybatis源码中大量用了ThreadLocal来保证线程安全(类名中带sync可知),实际上这也和ThreadLocal在线程安全中特殊的用空间换时间定位有关。

## 2.从何处来?

1.1 mapper中的sqlSession

回顾一下我们的sqlSession,在mapperProxy中已经确定了。

再往上查找,我们发现sqlSession是在mapperProxy构造方法中确定的:

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

而代理的创建,是在上层mapperProxyFactory里的:

public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

这里也已经指定了,那么再往上在mapperRegistry,我们也发现这里也是作为参数指定的:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

再往上,我们找到了Configuration里:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

而这里,是被sqlSession接口的实现类所调用的:

  <T> T getMapper(Class<T> type);

实际上这里都是用

configuration.getMapper(type, this);

来传入的。

此时我们知道了一件事情:

  • 我们使用的mapper,里面的sqlSession是在mapper生成之前已经存在了的。

我们来看看跟SqlSession的类,都有哪些:

  • sqlSessionFactory

    • sqlSessionFactoryBuilder
  • SqlSessionManager

  • sqlSessionFactoryBean

    • MybatisAutoConfiguration

我们先看看SqlSessionFactory和sqlSessionFactoryBuilder。

SqlSessionFactory

实际上sqlSessionFactoryBuilder的作用,就是通过各种流或者配置返回一个SqlSessionFactory对象。

那我们重点来看看sqlSessionFactory,打开接口就能被一排openSession震撼到。

sqlSessionFactory有两个实现类:

  • DefaultSqlSessionFactory和SqlSessionManager。

SqlSessionManager和其他的接口也有相关,我们先看看这个默认的实现DefaultSqlSessionFactory.。

DefaultSqlSessionFactory

大致浏览实现之后,可以发现关键的代码都在openSessionFromX这里:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    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();
  }
}
​
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();
  }
}

同时我们可以发现,在不指定一些参数的时候

  • 包括:execType

都是从配置Configuration类中,获取默认缺省值来的。

这里我们可以发现一些有趣的细节,对我们后续更广的探索有帮助:

  • sqlSession本身实际上和连接是解耦的,连接被包含在事务中,事务被包含在executor中。

SqlSessionManager

其实sqlSessionManager就是提供了一个sqlSessionFactory+sqlSession的缝合怪,具体的实现都是通过聚合的sqlSessionFactory和sqlSession提供的。

内部提供了一个localSqlSession做线程安全,但只在调用commit和rollback的时候会使用,其他时候用的是sqlSessionProxy的方法。

public class SqlSessionManager implements SqlSessionFactory, SqlSession {
​
  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;
​
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

初始化

MybatisAutoConfiguration implements InitializingBean

\