Mybatis源码解析03-关键组件SqlSession

执行流程简单回顾

前面介绍了

但是对mybatis执行SQL的核心(比如:StatementHandler、ParameterHandler等)还未过多涉及,接下来深入去看一下整个过程中一些关键的步骤。
下面是insert/update/delete的执行过程,因为这些操作返回的是影响行数(affected rows)。
mybatis_execute.drawio.png
而select的情况要复杂一些,有selectOne、selectList、selectMap、selectCursor。返回值的类型有单个的Mapped Object、或者一个List集合,或者一个Map类型,也有可能是一个Cursor(游标)更或者没有返回值(void)。以下以List类型为例来看一下这整个流程:
mybatis_execute_select.drawio.png
可以看到涉及的关键有:

  • SqlSession 通过此接口可以执行命令、获取Mappers、管理事务
  • Executor 执行器
  • StatementHandler mybatis用jdbc的Statement之间的交互
  • ParameterHandler 参数处理
  • ResultSetHandler 结果集处理
关键接口SqlSession

可以看一下Mybatis的官网介绍:SqlSession

The primary Java interface for working with MyBatis is the SqlSession. Through this interface you can execute commands, get mappers and manage transactions.
NOTE When using MyBatis with a dependency injection framework like Spring or Guice, SqlSessions are created and injected by the DI framework so you don’t need to use the SqlSessionFactoryBuilder or SqlSessionFactory and can go directly to the SqlSession section. Please refer to the MyBatis-Spring or MyBatis-Guice manuals for further info.

SqlSession是Mybatis的主要Java接口,通过此接口你可以执行命令、获取Mappers、管理事务。需要注意的是,如果使用了如Spring或者Guice这类DI框架,则不需要用SqlSessionFactoryBuilder or SqlSessionFactory,自己去管理SqlSession了,可以通过框架直接使用SqlSession,在整合Spring之后,一般使用SqlSession的另一个实现类:SqlSessionTemplate
image.png
如上图所示,在真正结合Spring使用的时候,SqlSession的实现类多了一个SqlSessionTemplate(在某些特殊场景,会根据SqlSessionTemplate直接通过jdbc去执行特殊的SQL)。

  • DefaultSqlSession
    默认的SqlSession,具体功能实现都是在此类中完成,但是是非线程安全的(因为Mybatis的一级缓存是session级的,从BaseExecutor#queryFromDatabase可知,会往缓存放一个占位对象(EXECUTION_PLACEHOLDER),如下所示:
localCache.putObject(key, EXECUTION_PLACEHOLDER);

而当一个线程刚执行完pubObject,另外一个线程执行到BaseExecutor#query的过程中,调用localCache.getObject(key) 时,得到的只是一个EXECUTION_PLACEHOLDER,此时就会出现如下异常(有兴趣可以自行实验一下)。

(java.lang.ClassCastException:org.apache.ibatis.executor.ExecutionPlaceholder cannot be cast to java.util.List)。
  • SqlSessionManager
  • SqlSessionTemplate
    从源码里可以看到SqlSessionManager与SqlSessionTemplate的构造函数:
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {  
  this.sqlSessionFactory = sqlSessionFactory;  
  this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(  
      SqlSessionFactory.class.getClassLoader(),  
      new Class[]{SqlSession.class},  
      new SqlSessionInterceptor());  
}
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());  
}

SqlSessionManager与SqlSessionTemplate都是通过代理的方式来处理线程安全的问题,可以比较两个SqlSessionInterceptor类的invoke方法

// SqlSessionManager
private class SqlSessionInterceptor implements InvocationHandler {  

  @Override  
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
    final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();  
    if (sqlSession != null) {  
		...
    } else {  
      try (SqlSession autoSqlSession = openSession()) {   
      ...
    }  
  }  
}
// SqlSessionTemplate
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);  
	...
  }  
	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);  
	  // 从SessionHolder获取
	  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;  
	}

}

可以看到大概流程都是将SqlSession放到ThreadLocal中,每个线程对应一个该线程私有的SqlSession来实现线程安全(Thread-safe)。

最后总结一下,SqlSessionManager与SqlSessionTemplate通过代理的方式,来增强DefaultSqlSession。后续我们在实际使用SqlSessionTemplate的时候,更能得心应手。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值