Mybatis源码阅读(三)-Spring集成Mybatis

本文探讨了DefaultSqlSession的线程不安全问题,重点介绍了SqlSessionTemplate如何通过ThreadLocal实现线程安全,并讲解了MapperFactoryBean在整合过程中的角色。同时提到了SqlSessionManager的补充作用。

一、DefaultSqlSession的线程安全问题

在这里插入图片描述
从DefaultSqlSession类的注释可知,它不是一个线程安全的对象,通过DefaultSqlSessionFactory创建使用之后必须立即销毁,不能复用,只能局限于request或者方法的范围。而且在同一个Spring事务内的SqlSession只能有一个,所以Mybatis如果想集成到Spring就必须解决线程安全的问题!

二、SqlSessionTemplate

SqlSessionTemplate是Mybatis为了接入Spring提供的Bean,它通过TransactionSynchronizationManager中的ThreadLocal保存线程对应的SqlSession,实现SqlSession的线程安全!

SqlSessionTemplate通过sqlSessionProxy来执行CRUD,而sqlSessionProxy又是通过内部类SqlSessionInterceptor生成的代理,先看下SqlSessionInterceptor的类的继承关系:
在这里插入图片描述
由图可知,SqlSessionInterceptor是一个InvocationHandler,直接看invoke()源码:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  // 获取sqlSession
  SqlSession sqlSession = getSqlSession(
	  SqlSessionTemplate.this.sqlSessionFactory,
	  SqlSessionTemplate.this.executorType,
	  SqlSessionTemplate.this.exceptionTranslator);
  try {
  
	// 执行CRUD
	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) {
	
	  // 关闭sqlSession,如果是事务方法则释放,不关闭
	  closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
	}
  }
}

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,从ThreadLocal获取
    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;
}

由上述源码可以看到SqlSessionTemplate 通过ThreadLocal实现SqlSession的线程安全问题,并且管理了SqlSession的生命周期,不需要我们手动创建、提交或释放!
注意:
1、connection 和 session不是一个概念,一个connection连接可以有多次会话!此处没有释放或关闭connection

2、由上面源码可以看出,如果不是在事务方法中,则每次查询都会获取一个新的SqlSession,这样一级缓存就会失效了,而事务是SqlSession级别的(SqlSession的commit和释放),在一个事务方法中SqlSession被复用了,这样一级缓存才生效
另外,Mybatis自身也提供了一个保证SqlSession的线程安全的类-SqlSessionManager,但是由于SqlSessionTemplate 已存在,所以维护较少,可以自行了解下。

三、MapperFactoryBean

Mapper接口如果有实现类,应该会是什么样子呢?可以看看下面这个实现类例子:

public class UserDaoImpl implements UserDao {

  private SqlSession sqlSession;

  // 需要传入sqlSession
  public void setSqlSession(SqlSession sqlSession) {
    this.sqlSession = sqlSession;
  }

  public User getUser(String userId) {
  	// 这就是最原始的方式
    return sqlSession.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
  }
}

而MapperFactoryBean其实就是对Mapper接口和SqlSession进行封装的FactoryBean,看下这个xml配置:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  // 这个Mapper接口在后续通过MapperProxy创建动态代理会用到
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  // 也可以是SqlSessionTemplate
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

由上面的xml配置可以看到,userMapper的Classs类型是MapperFactoryBean,而这个就是注入到Spring IOC容器中的Class类型。通过xml方式一个个配置太麻烦,MapperScannerConfigurer是可以进行包扫描配置,后续的文章再详细讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值