试问Java中 同一个方法中Mybatis多次请求数据库是否会创建多个会话

最近在开发过程中,小伙伴们有一个疑问:我们在写一个服务层的方法需要多次请求mybatis的dao(即mybatis的Mapper)方法,那会不会因为频繁跟数据库交互导致性能走低呢?
跟着疑问我们结合demo分析下mybatis的源码,来一层层解开大家的疑惑

第一步:调用dao层的方法上不加事务@transactional
在这里插入图片描述
从日志可以看出,在没有加事务的情况下,确实是Mapper的每次请求数据库,都会创建一个SqlSession与数据库交互

第二步:我们再看看加了事务的情况:
在这里插入图片描述

从日志可以看出,在方法中加了事务后,两次请求只创建了一个SqlSession

看到以上两个日志对比图,我们可能会问为什么加了事务就公用一个Sqlsession,好,别急,下面我们会根据源码给大家一一解析

Mapper的实现类是一个代理,真正执行逻辑的是MapperProxy.invoke(),该方法最终执行的是sqlSessionTemplate。

org.mybatis.spring.SqlSessionTemplate:

private final SqlSession sqlSessionProxy;

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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
这个是创建SqlSessionTemplate的最终构造方法,可以看出sqlSessionTemplate中用到了SqlSession,是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);
}
}
}
}
Mapper所有的方法,最终都会用这个方法来处理所有的数据库操作,其实spring整合mybatis和mybatis单独使用其实没区别,区别就是spring封装了所有处理细节,你就不用写大量的冗余代码,专注于业务开发。

该动态代理方法主要做了以下处理:

1、根据当前条件获取一个SqlSession,此时SqlSession可能是新创建的也有可能是获取到上一次请求的SqlSession;
2、反射执行SqlSession方法,再判断当前会话是否是一个事务,如果是一个事务,则不commit;
3、如果此时抛出异常,判断如果是PersistenceExceptionTranslator且不为空,那么就关闭当前会话,并且将sqlSession置为空防止finally重复关闭,PersistenceExceptionTranslator是spring定义的数据访问集成层的异常接口;
4、finally无论怎么执行结果如何,只要当前会话不为空,那么就会执行关闭当前会话操作,关闭当前会话操作又会根据当前会话是否有事务来决定会话是释放还是直接关闭。

org.mybatis.spring.SqlSessionUtils#getSqlSession:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(“Creating a new SqlSession”);
}

session = sessionFactory.openSession(executorType);

registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

return session;
}
是不是看到了不服跑个demo时看到的日志“Creating a new SqlSession”了。在这个方法当中,首先是从TransactionSynchronizationManager(以下称当前线程事务管理器)获取当前线程threadLocal是否有SqlSessionHolder,如果有就从SqlSessionHolder取出当前SqlSession,如果当前线程threadLocal没有SqlSessionHolder,就从sessionFactory中创建一个SqlSession,具体的创建步骤上面已经说过了,接着注册会话到当前线程threadLocal中。

先来看看当前线程事务管理器的结构:

public abstract class TransactionSynchronizationManager {
// …
// 存储当前线程事务资源,比如Connection、session等
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>(“Transactional resources”);
// 存储当前线程事务同步回调器
// 当有事务,该字段会被初始化,即激活当前线程事务管理器
private static final ThreadLocal<Set> synchronizations =
new NamedThreadLocal<>(“Transaction synchronizations”);
// …
}
这是spring的一个当前线程事务管理器,它允许将当前资源存储到当前线程ThreadLocal中,从前面也可看出SqlSessionHolder是保存在resources中。
org.mybatis.spring.SqlSessionUtils#registerSessionHolder:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
// 判断当前是否有事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
// 判断当前环境配置的事务管理工厂是否是SpringManagedTransactionFactory(默认)
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(“Registering transaction synchronization for SqlSession [” + session + “]”);
}

  holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
  // 绑定当前SqlSessionHolder到线程ThreadLocal中
  TransactionSynchronizationManager.bindResource(sessionFactory, holder);
  // 注册SqlSession同步回调器
  TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
  holder.setSynchronizedWithTransaction(true);
  // 会话使用次数+1
  holder.requested();
} else {
  if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
    }
  } else {
    throw new TransientDataAccessResourceException(
      "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
  }
}

} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(“SqlSession [” + session + “] was not registered for synchronization because synchronization is not active”);
}
}
}

注册SqlSession到当前线程事务管理器的条件首先是当前环境中有事务,否则不注册,判断是否有事务的条件是synchronizations的ThreadLocal是否为空:

public static boolean isSynchronizationActive() {
return (synchronizations.get() != null);
}
每当我们开启一个事务,会调用initSynchronization()方法进行初始化synchronizations,以激活当前线程事务管理器。

public static void initSynchronization() throws IllegalStateException {
if (isSynchronizationActive()) {
throw new IllegalStateException(“Cannot activate transaction synchronization - already active”);
}
logger.trace(“Initializing transaction synchronization”);
synchronizations.set(new LinkedHashSet());
}
所以当前有事务时,会注册SqlSession到当前线程ThreadLocal中。

Mybatis自己也实现了一个自定义的事务同步回调器SqlSessionSynchronization,在注册SqlSession的同时,也会将SqlSessionSynchronization注册到当前线程事务管理器中,它的作用是根据事务的完成状态回调来处理线程资源,即当前如果有事务,那么当每次状态发生时就会回调事务同步器,具体细节可移步至Spring的org.springframework.transaction.support包。

回到SqlSessionInterceptor代理类的逻辑,发现判断会话是否需要提交要调用以下方法:

org.mybatis.spring.SqlSessionUtils#isSqlSessionTransactional:

public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

return (holder != null) && (holder.getSqlSession() == session);
}
取决于当前SqlSession是否为空并且判断当前SqlSession是否与ThreadLocal中的SqlSession相等,前面也分析了,如果当前没有事务,SqlSession是不会保存到事务同步管理器的,即没有事务,会话提交。

org.mybatis.spring.SqlSessionUtils#closeSqlSession:

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)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(“Releasing transactional SqlSession [” + session + “]”);
}
holder.released();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(“Closing non transactional SqlSession [” + session + “]”);
}
session.close();
}
}
方法无论执行结果如何都需要执行关闭会话逻辑,这里的判断也是判断当前是否有事务,如果SqlSession在事务当中,则减少引用次数,没有真实关闭会话。如果当前会话不存在事务,则直接关闭会话。

最后总结
涉及到了Spring的自定义事务的一些机制,其中当前线程事务管理器是整个事务的核心与中轴,当前有事务时,会初始化当前线程事务管理器的synchronizations,即激活了当前线程同步管理器,当Mybatis访问数据库会首先从当前线程事务管理器获取SqlSession,如果不存在就会创建一个会话,接着注册会话到当前线程事务管理器中,如果当前有事务,则会话不关闭也不commit,Mybatis还自定义了一个TransactionSynchronization,用于事务每次状态发生时回调处理。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值