mybatis与spring整合时是如何解决sqlsession线程不安全的问题的

我们知道mybatis的sqlsession接口默认的实现类是DefaultSqlsession,作者在该类上面注释了是线程非安全的:

 那么到底为何说它是线程不安全的呢?

1.首先由于JDBC的Connection对象本身不是线程安全的,而session中又只有一个connection,所以不是线程安全的
一次SqlSession的执行最终只会产生一个connection,所以我们设想一下,在两个线程通过同一个sqlsession来执行crud,那么就有可能,我先跑完的线程,把唯一的这一个连接给关闭掉,从而造成另一条线程的逻辑不被成功执行,所以通过DefaultSqlSession来执行数据库操作是线程不安全的。
2.一级缓存 由于一级缓存是session级别的,所以如果多个线程同时使用session,当线程A进行了插入操作未完成,但是此时线程B进行查询并插入一级缓存,那么一级缓存与数据库中数据不一致
 

那么spring和mybatis整合的时候是如何解决这个不安全的问题呢,先说答案,我们再spring在整合mybatis的时候是通过sqlsessiontemplate来操作crud的,那么关键就是sqlsessiontemplate这个类了,它里面采用了threadlocal的方式保证每个线程独享一份自己的sqlsession,这样也就解决了我们说的线程安全问题,且跟我一起看源码,细细道来

首先我们看sqlsessiontemplate的构造方法:

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

其中sqlsessionProxy其实就是一个sqlsession,那么它在实例化的时候是通过jdk动态代理生成的,我们知道动态代理最终会调用到对应的interceptor的invoke方法:

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);
      }
    }
  }
}

这个方法其实就是一个事务的执行过程,先获取sqlsession然后执行sql,最后commit,如果异常就向上抛出异常,并且关闭session

关键就在第一行,怎么去获取sqlseission了,继续看(org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)):

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;
  }

  LOGGER.debug(() -> "Creating a new SqlSession");
  session = sessionFactory.openSession(executorType);

  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

继续看registerSessionHolder这个方法:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
  SqlSessionHolder holder;
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    Environment environment = sessionFactory.getConfiguration().getEnvironment();

    if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
      LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

      holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
      TransactionSynchronizationManager.bindResource(sessionFactory, holder);
      TransactionSynchronizationManager
          .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
      holder.setSynchronizedWithTransaction(true);
      holder.requested();
    } else {
      if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
        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 {
    LOGGER.debug(() -> "SqlSession [" + session
        + "] was not registered for synchronization because synchronization is not active");
  }

}
SqlSessionHolder里面是持有了一个创建出来的sqlsession对象,
TransactionSynchronizationManager.bindResource(sessionFactory, holder);

这一行就是把SqlSessionHolder放到一个TransactionSynchronizationManager的threadlocal里面去了:

public static void bindResource(Object key, Object value) throws IllegalStateException {
   Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
   Assert.notNull(value, "Value must not be null");
   Map map = (Map) resources.get();
   // set ThreadLocal Map if none found
   if (map == null) {
      map = new HashMap();
      resources.set(map);
   }
   if (map.put(actualKey, value) != null) {
      throw new IllegalStateException("Already value [" + map.get(actualKey) + "] for key [" +
            actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
   }
   if (logger.isTraceEnabled()) {
      logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
            Thread.currentThread().getName() + "]");
   }
}

resources就是一个threadlocal,到此就可以知道,sqlsessiontemplate解决sqlsession线程安全的问题就是通过threadlocal来实现每一个线程独享一份sqlsession

前面有讲到在执行完业务sql之后,如果异常了会调用一个closesession来关闭sqlsession,它里面最后是调用的mybatis的sqlsessionmanager的close方法,sqlsessionmanager也是通过threadlocal来实现线程安全的,至于为什么是调用sqlsessionmanager,这个是mybatis与spring整合的时候实例化sqlsession的策略决定的,这个看mybatis源码就知道实例化sqlsession的时候有两种不同的策略

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值