DefaultSqlSession是线程不平安的

在Mybatis中SqlSession是提供给内部调用的顶层接口,实现类有:DefaultSqlSession、SqlSessionManager以及mybatis-spring提供的实现SqlSessionTemplate。默认实现类为DefaultSqlSession,是线程不齐全的。类结构图如下:

对于Mybatis提供的原生实现类来说,用的最多就是DefaultSqlSession,然而咱们晓得DefaultSqlSession这个类不是线程平安的!如下:

SqlSessionTemplate是如何保障线程平安的

在咱们平时的开发中通常会用到Spring,也会用到mybatis-spring框架,在Spring集成Mybatis的时候咱们能够用到SqlSessionTemplate(Spring提供的SqlSession实现类),应用场景案例如下:

查看SqlSessionTemplate的源码正文如下:

通过源码正文能够看到SqlSessionTemplate是线程平安的类,并且实现了SqlSession接口,也就是说咱们能够通过SqlSessionTemplate来代替以往的DefaultSqlSession实现对数据库CRUD操作,并且还保障单例线程平安,那么它是如何保障线程平安的呢?

首先,通过SqlSessionTemplate领有的三个重载的构造方法剖析,最终都会调用最初一个构造方法,会初始化一个SqlSessionProxy的代理对象,如果调用代理类实例中实现的SqlSession接口中定义的办法,该调用会被导向SqlSessionInterceptor的invoke办法触发代理逻辑

接下来查看SqlSessionInterceptor的invoke办法

  1. 通过getSqlSession办法获取SqlSession对象(如果应用了事务,从Spring事务上下文获取)
  2. 调用SqlSession的接口办法操作数据库获取后果
  3. 返回后果集
  4. 若产生异样则转换后抛出异样,并最终敞开SqlSession对象
private class SqlSessionInterceptor implements InvocationHandler {    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {          //获取SqlSession(这个sqlSession才是真正应用的,它不是线程平安的)                //这个办法能够依据Spring的事务上下文来获取事务范畴内的SqlSession                SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);      try {                //调用sqlSession对象的办法(select、update等)        Object result = method.invoke(sqlSession, args);                //判断是否为事务操作,如果未被Spring事务托管则主动提交commit        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {          sqlSession.commit(true);        }        return result;      } catch (Throwable t) {                //如果出现异常则依据状况转换后抛出        Throwable unwrapped = unwrapThrowable(t);        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);          sqlSession = null;          Throwable translated = SqlSessionTemplate.this.exceptionTranslator              .translateExceptionIfPossible((PersistenceException) unwrapped);          if (translated != null) {            unwrapped = translated;          }        }        throw unwrapped;      } finally {                //最终敞开sqlSession对象        if (sqlSession != null) {          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);        }      }    }  }

重点剖析getSqlSession办法如下:

  1. 若无奈从以后线程的ThreadLocal中获取则通过SqlSessionFactory获取SqlSession
  2. 若开启了事务,则从以后线程的ThrealLocal上下文中获取SqlSessionHolder
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,      PersistenceExceptionTranslator exceptionTranslator) {    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);        //若开启了事务反对,则从以后的ThreadLocal上下文中获取SqlSessionHolder        //SqlSessionHolder是SqlSession的包装类    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);    SqlSession session = sessionHolder(executorType, holder);    if (session != null) {      return session;    }    LOGGER.debug(() -> "Creating a new SqlSession");        //若无奈从ThrealLocal上下文中获取则通过SqlSessionFactory获取SqlSession    session = sessionFactory.openSession(executorType);        //若为事务操作,则注册SqlSessionHolder到ThrealLocal中    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);    return session;  }

大抵的剖析到此为止,文中只对次要的过程进行了大抵的阐明,小伙伴若想要仔细分析,能够本人关上源码走一遍!

SqlSessionManger又是什么?

SqlSessionManager是Mybatis提供的线程平安的操作类,且看定义如下:

通过上图能够发现SqlSessionManager的构造方法居然是private的,那咱们怎么创建对象呢?其实SqlSessionManager创建对象是通过newInstance办法创建对象的,但须要注入它尽管是公有的构造方法,并且提供给咱们一个私有的newInstance办法,但它并不是一个单例模式!

newInstance有很多重载办法,如下所示:

public static SqlSessionManager newInstance(Reader reader) {  return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));}public static SqlSessionManager newInstance(Reader reader, String environment) {  return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null));}public static SqlSessionManager newInstance(Reader reader, Properties properties) {  return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, properties));}public static SqlSessionManager newInstance(InputStream inputStream) {  return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null));}public static SqlSessionManager newInstance(InputStream inputStream, String environment) {  return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, environment, null));}public static SqlSessionManager newInstance(InputStream inputStream, Properties properties) {  return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, properties));}public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {  return new SqlSessionManager(sqlSessionFactory);}

SqlSessionManager的openSession办法及其重载办法是间接通过调用底层封装SqlSessionFactory对象的openSession办法来创立SqlSession对象的,如下所示:

@Overridepublic SqlSession openSession(boolean autoCommit) {  return sqlSessionFactory.openSession(autoCommit);}@Overridepublic SqlSession openSession(Connection connection) {  return sqlSessionFactory.openSession(connection);}@Overridepublic SqlSession openSession(TransactionIsolationLevel level) {  return sqlSessionFactory.openSession(level);}@Overridepublic SqlSession openSession(ExecutorType execType) {  return sqlSessionFactory.openSession(execType);}@Overridepublic SqlSession openSession(ExecutorType execType, boolean autoCommit) {  return sqlSessionFactory.openSession(execType, autoCommit);}@Overridepublic SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {  return sqlSessionFactory.openSession(execType, level);}@Overridepublic SqlSession openSession(ExecutorType execType, Connection connection) {  return sqlSessionFactory.openSession(execType, connection);}

SqlSessionManager中实现SqlSession接口中的办法,例如:select、update等,都是间接调用SqlSessionProxy代理对象中相应的办法,在创立该代理对像的时候应用的InvocationHandler对象是SqlSessionInterceptor,他是定义在SqlSessionManager的一个外部类,其定义如下:

private class SqlSessionInterceptor implements InvocationHandler {  public SqlSessionInterceptor() {      // Prevent Synthetic Access  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    //获取以后ThreadLocal上下文的SqlSession        final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();    if (sqlSession != null) {      try {                //从上下文获取到SqlSession之后调用对应的办法        return method.invoke(sqlSession, args);      } catch (Throwable t) {        throw ExceptionUtil.unwrapThrowable(t);      }    } else {            //如果无奈从ThreadLocal上下文中获取SqlSession则新建一个SqlSession      try (SqlSession autoSqlSession = openSession()) {        try {          final Object result = method.invoke(autoSqlSession, args);          autoSqlSession.commit();          return result;        } catch (Throwable t) {          autoSqlSession.rollback();          throw ExceptionUtil.unwrapThrowable(t);        }      }    }  }}

此处咱们在思考下ThreadLocal的localSqlSession对象在什么时候赋值对应的SqlSession,往上查找最终定位代码(若调用startManagerSession办法将设置ThreadLocal的localSqlSession上下文中的SqlSession对象),如下所示:

public void startManagedSession() {  this.localSqlSession.set(openSession());}public void startManagedSession(boolean autoCommit) {  this.localSqlSession.set(openSession(autoCommit));}public void startManagedSession(Connection connection) {  this.localSqlSession.set(openSession(connection));}public void startManagedSession(TransactionIsolationLevel level) {  this.localSqlSession.set(openSession(level));}public void startManagedSession(ExecutorType execType) {  this.localSqlSession.set(openSession(execType));}public void startManagedSession(ExecutorType execType, boolean autoCommit) {  this.localSqlSession.set(openSession(execType, autoCommit));}public void startManagedSession(ExecutorType execType, TransactionIsolationLevel level) {  this.localSqlSession.set(openSession(execType, level));}public void startManagedSession(ExecutorType execType, Connection connection) {  this.localSqlSession.set(openSession(execType, connection));}

SqlSessionTemplate与SqlSessionManager的分割与区别

  • SqlSessionTemplate是Mybatis为了接入Spring提供的Bean。通过TransactionSynchronizationManager中的ThreadLocal<Map<Object, Object>>保留线程对应的SqlSession,实现session的线程平安。
  • SqlSessionManager是Mybatis不接入Spring时用于治理SqlSession的Bean。通过SqlSessionManagger的ThreadLocal<SqlSession>实现session的线程平安。

总结剖析

通过下面的代码剖析,咱们能够看出Spring解决SqlSession线程平安问题的思路就是动静代理与ThreadLocal的使用,咱们能够举一反三:当遇到线程不平安的类,然而又想当作线程平安的类应用,则能够应用ThreadLocal进行线程上下文的隔离,此处的动静代理技术更好的解决了下层API调用的非侵入性,保障API接口调用的高内聚、低耦合准则

本文由博客一文多发平台 OpenWrite 公布!