面试真的是越来越难了,一直关注我的朋友应该清楚,小编有一个习惯,喜欢每年都去面试一下,试试水也看看自己的缺陷,这不,前几天有一个机会,让小编成功的被刺激了一耙(虽然知道这样不是很好,但是还是想操作一下,给各位 hr 道歉)
事情是这样的,小编的简历一直挂在网上,然后有人给我打电话,说有没有时间去面试一下,刚过完双十一,闲着也是闲着,我就应下来去了,然后前面谈的都不错,直到要结束最后一下面试,马上就是 hr 面了,面试官脑回路不知道怎么回事,就问了我一个意想不到的问题:看你简历写的精通 mybatis 源码,那你能不能给我讲一下 sqlsession 啊,WTF?我就反问一句,这有点多啊,那我主要说那一块呢?
1、SqlSession 操作模板实现类==SqlSessionTemplate
2、用于保存当前 SqlSession 对象==SqlSessionInterceptor
3、SqlSession 的事务同步器==SqlSessionSynchronization
幸好我之前看过这一块,然后磕磕绊绊的讲完了,面试官眼神复杂的看了我一下,说可以了,先这样吧。。。。
公众号:Java 架构师联盟,每日更更新技术好文
偶,你 NNGT,我这美好的一天,就这么没了,面试完了也没啥心思去吃好吃的,回到家就开始冲浪,将这几个知识点进行整理
其实官网上,sqlsession 相关得知识还是很多的,但是时间问题,就针对源码整理被问到的这几个知识点,大家有什么其他想看的可以评论区告诉我,我会抽时间进行整理,好了,话不多说,看重点
SqlSessionTemplate
org.mybatis.spring.SqlSessionTemplate:实现 SqlSession 和 DisposableBean 接口,SqlSession 操作模板实现类
实际上,代码实现和 org.apache.ibatis.session.SqlSessionManager 相似,承担 SqlSessionFactory 和 SqlSession 的职责
构造方法
public class SqlSessionTemplate implements SqlSession, DisposableBean {
复制代码
* a factory of SqlSession
复制代码
private final SqlSessionFactory sqlSessionFactory;
复制代码
* {@link Configuration} 中默认的 Executor 执行器类型,默认 SIMPLE
复制代码
private final ExecutorType executorType;
复制代码
* SqlSessionInterceptor 代理对象
复制代码
private final SqlSession sqlSessionProxy;
复制代码
* 异常转换器,MyBatisExceptionTranslator 对象
复制代码
private final PersistenceExceptionTranslator exceptionTranslator;
复制代码
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
复制代码
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
复制代码
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
复制代码
this(sqlSessionFactory, executorType,
复制代码
new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
复制代码
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
复制代码
PersistenceExceptionTranslator exceptionTranslator) {
复制代码
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
复制代码
notNull(executorType, "Property 'executorType' is required");
复制代码
this.sqlSessionFactory = sqlSessionFactory;
复制代码
this.executorType = executorType;
复制代码
this.exceptionTranslator = exceptionTranslator;
复制代码
// 创建一个 SqlSession 的动态代理对象,代理类为 SqlSessionInterceptor
复制代码
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
复制代码
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
复制代码
sqlSessionFactory:用于创建 SqlSession 对象
executorType:执行器类型,创建 SqlSession 对象是根据它创建对应的 Executor 执行器,默认为
sqlSessionProxy:SqlSession 的动态代理对象,代理类为 SqlSessionInterceptor
exceptionTranslator:异常转换器
在调用 SqlSessionTemplate 中的 SqlSession 相关方法时,内部都是直接调用 sqlSessionProxy 动态代理对象的方法,我们来看看是如何处理的
SqlSessionInterceptor
SqlSessionTemplate 的内部类,实现了 InvocationHandler 接口,作为 sqlSessionProxy 动态代理对象的代理类,对 SqlSession 的相关方法进行增强
代码如下:
private class SqlSessionInterceptor implements InvocationHandler {
复制代码
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
复制代码
// <1> 获取一个 SqlSession 对象
复制代码
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
复制代码
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
复制代码
Object result = method.invoke(sqlSession, args);
复制代码
// 当前 SqlSession 不处于 Spring 托管的事务中
复制代码
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
复制代码
// force commit even on non-dirty sessions because some databases require
复制代码
// a commit/rollback before calling close()
复制代码
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);
复制代码
// 对异常进行转换,差不多就是转换成 MyBatis 的异常
复制代码
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
复制代码
.translateExceptionIfPossible((PersistenceException) unwrapped);
复制代码
if (translated != null) {
复制代码
if (sqlSession != null) {
复制代码
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
复制代码
调用 SqlSessionUtils 的 getSqlSession 方法,获取一个 SqlSession 对象
执行 SqlSession 的方法
当前 SqlSession 不处于 Spring 托管的事务中,则强制提交
调用 SqlSessionUtils 的 closeSqlSession 方法,“关闭”SqlSession 对象,这里的关闭不是真正的关闭
SqlSessionHolder
org.mybatis.spring.SqlSessionHolder:继承 org.springframework.transaction.support.ResourceHolderSupport 抽象类,SqlSession 持有器,用于保存当前 SqlSession 对象,保存到 org.springframework.transaction.support.TransactionSynchronizationManager 中,代码如下:
public final class SqlSessionHolder extends ResourceHolderSupport {
复制代码
private final SqlSession sqlSession;
复制代码
private final ExecutorType executorType;
复制代码
* PersistenceExceptionTranslator 对象
复制代码
private final PersistenceExceptionTranslator exceptionTranslator;
复制代码
public SqlSessionHolder(SqlSession sqlSession, ExecutorType executorType,
复制代码
PersistenceExceptionTranslator exceptionTranslator) {
复制代码
notNull(sqlSession, "SqlSession must not be null");
复制代码
notNull(executorType, "ExecutorType must not be null");
复制代码
this.sqlSession = sqlSession;
复制代码
this.executorType = executorType;
复制代码
this.exceptionTranslator = exceptionTranslator;
复制代码
SqlSessionUtils
org.mybatis.spring.SqlSessionUtils:SqlSession 工具类,负责处理 MyBatis SqlSession 的生命周期,借助 Spring 的 TransactionSynchronizationManager 事务管理器管理 SqlSession 对象
getSqlSession 方法
getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)方法,注释如下:
Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of current transaction.If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the transaction if Spring TX is active and SpringManagedTransactionFactory is configured as a transaction manager.
从事务管理器(线程安全)中获取一个 SqlSession 对象,如果不存在则创建一个 SqlSession,然后注册到事务管理器中,方法如下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
复制代码
PersistenceExceptionTranslator exceptionTranslator) {
复制代码
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
复制代码
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
复制代码
// 从 Spring 事务管理器中获取一个 SqlSessionHolder 对象
复制代码
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
复制代码
SqlSession session = sessionHolder(executorType, holder);
复制代码
LOGGER.debug(() -> "Creating a new SqlSession");
复制代码
// 上面没有获取到,则创建一个 SqlSession
复制代码
session = sessionFactory.openSession(executorType);
复制代码
// 将上面创建的 SqlSession 封装成 SqlSessionHolder,往 Spring 事务管理器注册
复制代码
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
复制代码
从 Spring 事务管理器中,根据 SqlSessionFactory 获取一个 SqlSessionHolder 对象
调用 sessionHolder 方法,获取到 SqlSession 对象,方法如下 private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) { SqlSession session = null; if (holder != null && holder.isSynchronizedWithTransaction()) { // 如果执行器类型发生了变更,抛出 TransientDataAccessResourceException 异常 if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException( "Cannot change the ExecutorType when there is an existing transaction"); } // 增加计数,关闭 SqlSession 时使用 holder.requested(); LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction"); // 获得 SqlSession 对象 session = holder.getSqlSession(); } return session; }
如果 SqlSession 对象不为 null,则直接返回,接下来会创建一个
上面没有获取到,则创建一个 SqlSession 对象
调用 registerSessionHolder 方法,将上面创建的 SqlSession 封装成 SqlSessionHolder,往 Spring 事务管理器注册
返回新创建的 SqlSession 对象
registerSessionHolder 方法
registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session)方法
注释如下:
Register session holder if synchronization is active (i.e. a Spring TX is active).
Note: The DataSource used by the Environment should be synchronized with the transaction either through DataSourceTxMgr or another tx synchronization.
Further assume that if an exception is thrown, whatever started the transaction will handle closing / rolling back the Connection associated with the SqlSession.
如果事务管理器处于激活状态,则将 SqlSession 封装成 SqlSessionHolder 对象注册到其中,方法如下:
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
复制代码
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
复制代码
if (TransactionSynchronizationManager.isSynchronizationActive()) {
复制代码
Environment environment = sessionFactory.getConfiguration().getEnvironment();
复制代码
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
复制代码
LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
复制代码
// <1.1> 创建 SqlSessionHolder 对象
复制代码
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
复制代码
// <1.2> 绑定到 TransactionSynchronizationManager 中
复制代码
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
复制代码
// <1.3> 创建 SqlSessionSynchronization 到 TransactionSynchronizationManager 中
复制代码
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
复制代码
holder.setSynchronizedWithTransaction(true);
复制代码
// <2> 如果非 Spring 事务管理器,抛出 TransientDataAccessResourceException 异常
复制代码
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
复制代码
LOGGER.debug(() -> "SqlSession [" + session
复制代码
+ "] was not registered for synchronization because DataSource is not transactional");
复制代码
throw new TransientDataAccessResourceException(
复制代码
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
复制代码
LOGGER.debug(() -> "SqlSession [" + session
复制代码
+ "] was not registered for synchronization because synchronization is not active");
复制代码
如果使用 Spring 事务管理器,才会进行注册
创建 SqlSessionHolder 对象 holder
绑定到 TransactionSynchronizationManager 中,key 为 SqlSessionFactory 对象
创建 SqlSessionSynchronization 对象(事务同步器)到 TransactionSynchronizationManager 中
设置 holder 的 synchronizedWithTransaction 属性为 ture,和事务绑定了
增加 holder 的 referenceCount 引用数量
closeSqlSession 方法
closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)方法,注释如下:
Checks if SqlSession passed as an argument is managed by Spring TransactionSynchronizationManager
If it is not, it closes it, otherwise it just updates the reference counter and lets Spring call the close callback when the managed transaction ends
如果 SqlSessionFactory 是由 Spring 的事务管理器管理,并且和入参中的 session 相同,那么只进行释放,也就是将 referenceCount 引用数量减一,否则就直接关闭了
方法如下:
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
复制代码
notNull(session, NO_SQL_SESSION_SPECIFIED);
复制代码
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
复制代码
// <1> 从 TransactionSynchronizationManager 中,获得 SqlSessionHolder 对象
复制代码
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
复制代码
// <2.1> 如果相等,说明在 Spring 托管的事务中,则释放 holder 计数
复制代码
if ((holder != null) && (holder.getSqlSession() == session)) {
复制代码
LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
复制代码
// <2.2> 如果不相等,说明不在 Spring 托管的事务中,直接关闭 SqlSession 对象
复制代码
LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
复制代码
从事务管理器中,根据 SqlSessionFactory 获得 SqlSessionHolder 对象
如果相等,说明在 Spring 托管的事务中,则释放 holder 计数
否则,不在 Spring 托管的事务中,直接关闭 SqlSession 对象
isSqlSessionTransactional 方法
isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory)方法,判断 SqlSession 对象是否被 Sping 的事务管理器管理,代码如下:
public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
复制代码
notNull(session, NO_SQL_SESSION_SPECIFIED);
复制代码
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
复制代码
// 从 TransactionSynchronizationManager 中,获得 SqlSessionHolder 对象
复制代码
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
复制代码
// 如果相等,说明在 Spring 托管的事务中
复制代码
return (holder != null) && (holder.getSqlSession() == session);
复制代码
SqlSessionSynchronization
org.mybatis.spring.SqlSessionUtils 的内部类,继承了 TransactionSynchronizationAdapter 抽象类,SqlSession 的事务同步器,基于 Spring Transaction 体系
注释如下:
Callback for cleaning up resources.
It cleans TransactionSynchronizationManager and also commits and closes the SqlSession.
It assumes that Connection life cycle will be managed by DataSourceTransactionManager or JtaTransactionManager
回调的时候清理资源
构造方法
private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {
复制代码
private final SqlSessionHolder holder;
复制代码
private final SqlSessionFactory sessionFactory;
复制代码
private boolean holderActive = true;
复制代码
public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) {
复制代码
notNull(holder, "Parameter 'holder' must be not null");
复制代码
notNull(sessionFactory, "Parameter 'sessionFactory' must be not null");
复制代码
this.sessionFactory = sessionFactory;
复制代码
getOrder 方法
// order right before any Connection synchronization
复制代码
return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1;
复制代码
suspend 方法
当事务挂起时,取消当前线程的绑定的 SqlSessionHolder 对象,方法如下:
LOGGER.debug(() -> "Transaction synchronization suspending SqlSession [" + this.holder.getSqlSession() + "]");
复制代码
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
复制代码
resume 方法
当事务恢复时,重新绑定当前线程的 SqlSessionHolder 对象,方法如下:
LOGGER.debug(() -> "Transaction synchronization resuming SqlSession [" + this.holder.getSqlSession() + "]");
复制代码
TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder);
复制代码
beforeCommit 方法
在事务提交之前,调用 SqlSession#commit() 方法之前,提交事务。虽然说,Spring 自身也会调用 Connection#commit() 方法,进行事务的提交。但是,SqlSession#commit() 方法中,不仅仅有事务的提交,还有提交批量操作,刷新本地缓存等等,方法如下:
public void beforeCommit(boolean readOnly) {
复制代码
// Connection commit or rollback will be handled by ConnectionSynchronization or DataSourceTransactionManager.
复制代码
// But, do cleanup the SqlSession / Executor, including flushing BATCH statements so they are actually executed.
复制代码
// SpringManagedTransaction will no-op the commit over the jdbc connection
复制代码
// TODO This updates 2nd level caches but the tx may be rolledback later on!
复制代码
if (TransactionSynchronizationManager.isActualTransactionActive()) {
复制代码
LOGGER.debug(() -> "Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
复制代码
this.holder.getSqlSession().commit();
复制代码
} catch (PersistenceException p) {
复制代码
if (this.holder.getPersistenceExceptionTranslator() != null) {
复制代码
DataAccessException translated = this.holder.getPersistenceExceptionTranslator()
复制代码
.translateExceptionIfPossible(p);
复制代码
if (translated != null) {
复制代码
beforeCompletion 方法
提交事务完成之前,关闭 SqlSession 对象,在 beforeCommit 之后调用,方法如下:
public void beforeCompletion() {
复制代码
// Issue #18 Close SqlSession and deregister it now
复制代码
// because afterCompletion may be called from a different thread
复制代码
if (!this.holder.isOpen()) {
复制代码
LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
复制代码
// 取消当前线程的绑定的 SqlSessionHolder 对象
复制代码
TransactionSynchronizationManager.unbindResource(sessionFactory);
复制代码
this.holderActive = false;
复制代码
LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
复制代码
this.holder.getSqlSession().close();
复制代码
afterCompletion 方法
在事务完成之后,关闭 SqlSession 对象,解决可能出现的跨线程的情况,方法如下:
public void afterCompletion(int status) {
复制代码
if (this.holderActive) { // 处于有效状态
复制代码
// afterCompletion may have been called from a different thread
复制代码
// so avoid failing if there is nothing in this one
复制代码
LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
复制代码
// 取消当前线程的绑定的 SqlSessionHolder 对象
复制代码
TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
复制代码
this.holderActive = false;
复制代码
LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
复制代码
this.holder.getSqlSession().close();
复制代码
总结
我这次面试的公司不大,只能算是一家中型公司,也没有什么说面试前的准备,都是平时学习的记忆,毕竟目的不一样,但是,同样也反映出一个真实情况:在日常工作中要多注意一些细节,当出现问题的时候在解决完,有时间的情况下多深入看一下源码的东西,其实源码真的不难,嘿嘿嘿,好了,整理完了就觉得高兴多了,看时间还早,夜生活该开始了
祝周末愉快
评论