spring-mybatis如何实现线程安全

之前看网上都说,mybatis自带的DefaultSqlSession是线程不安全的,但是Spring-Mybatis整合之后,融入到框架中,SqlSession就变成线程安全的。但是对如何实现线程安全的,一直是不清楚,所以看源码吧。

首先先查看spring-mybatis的官方说明文档,地址:http://www.mybatis.org/spring/zh/,其中需要着重关注【第六章:注入映射器,MapperFactoryBean】。官方给的主要配置如下

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

然后查看MapperFactoryBean的声明

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>

该类是一个泛型类,可以理解为泛型T是这个类需要代理生成的对象类型。

下面继续关注配置中的两个成员变量,mapperInterface和sqlSessionFactory。mapperInterface是泛型T的具体类型,作用就如同类声明里面说的,接下来最重要的是sqlSessionFactory,下面看一下sqlSessionFactory的setter方法

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }

可以看到虽然配置的是SqlSessionFactory,但是最终作用的还是SqlSessionTemplate这个模板类,下钻到createSqlSessionTemplate方法,最后发现是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());
}

看到这,是否觉得有一行代码很熟悉?没错,就是下面这个基于jdk的动态代理

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

了解动态代理的同学应该明白,这个代理对象的关键拦截部分在于InvocationHandler的实现类,也就是这里的SqlSessionInterceptor类,而实现拦截的方法就在于InvocationHandler#invoke中

SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);//此时获取的是mybatis生成的DefaultSqlSession
      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的获取来自于getSqlSession方法,进入该方法分为两块主要内容,一个是通过SqlSessionFactory获取绑定的SqlSessionHolder(SqlSession的封装对象,参看类定义),如果SqlSessionHolder为空的话,就调用mybatis的接口生成一个SqlSession,然后将其封装为SqlSessionHolder对象,然后进行注册,那么线程安全的关键就在于这个注册了,所以查看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);//将sqlSession进行封装
                TransactionSynchronizationManager.bindResource(sessionFactory, holder);//内部数据结构是ThreadLocal<Map<factory,resource>>,将该Map与当前线程进行绑定
                TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
                holder.setSynchronizedWithTransaction(true);
                holder.requested();//当前线程的SqlSession的内部计数器+1
            } 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");
        }

    }

从代码的逻辑中可以看到,SqlSession的线程安全依赖于事务管理器TransactionSynchronizationManager,而结合代码中关键的一行

TransactionSynchronizationManager.bindResource(sessionFactory, holder);//内部数据结构是ThreadLocal<Map<factory,resource>>,将该Map与当前线程进行绑定

可以大致猜测到,如果当前线程中的SqlSession(实际上是被包装为SqlSessionHolder)未与SqlSessionFactory绑定,那么就使用Map<SqlSessionFactory,SqlSessionHolder>进行绑定关系,然后通过bindResource方法,存到本地线程变量中,代码如下

public static void bindResource(Object key, Object value) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Assert.notNull(value, "Value must not be null");
		Map<Object, Object> map = resources.get();//resource-->ThreadLocal<Map<SqlSessionFactory,SqlSessionHolder>>
		// set ThreadLocal Map if none found
		if (map == null) {
			map = new HashMap<>();
			resources.set(map);
		}
		Object oldValue = map.put(actualKey, value);
		// Transparently suppress a ResourceHolder that was marked as void...
		if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
			oldValue = null;
		}
		if (oldValue != null) {
			throw new IllegalStateException("Already value [" + oldValue + "] 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() + "]");
		}
	}

该方法的大致意思就是从当前线程的本地变量ThreadLocal中取出Map,如果不存在的话就将key和value塞入Map

所以方法走到现在,其实spring-mybatis如何实现线程安全的思路已经清晰了,通过ThreadLocal,确切的说是通过对象ThreadLocal<Map<SqlSessionFactory,SqlSessionHolder>>。

大致理一下流程:web容器接受请求,分配一个线程用于处理该请求(该线程中有能处理的请求的对象Controller/Service/DAO),最终操作数据库是通过DAO对象来的,而DAO对象是如何生成,在开篇已经介绍了,通过MapperFactoryBean包装生成,而这个DAO对象的数据库会话其实就是来自于SqlSessionFactory.getSqlSession方法,也就是mybatis的API获取,通过TransactionSynchronizedManager类以及其成员变量ThreadLocal实现将获取的SqlSession与SqlSessionFactory绑定并且关联到当前线程。

其实还有一点一直不是很清楚,到底什么是线程安全?

转载于:https://my.oschina.net/u/3145456/blog/1841572

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值