「这是我参与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
\