Mybatis 线程安全

SqlSessionFactory的open方法获取的是 DefaultSqlSession,但是在Spring中我们不能直接使用DefaultSqlSession,因为DefaultSqlSession是 线程不安全的。所以直接使用会存在数据安全问题,如果是两个线程同时方案, 一个线程插入数据, 一个线程修改数据, 因为是同一个连接, 就造成内存和数据库不一致现象, 所以线程不安全.

image.png

解决方案:

SqlSessionTemplate 在mybatis-spring的包中,提供了一个线程安全的SqlSession的包装类,用来替代SqlSession,这 个类就是SqlSessionTemplate。因为它是线程安全的,所以可以在所有的DAO层共享一个实例(默认是 单例的) 本地真正执行对象还是DefaultSqlSession

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);
        }
      }
    }
  }
  1. 重点: 从当前上下文获取sqlSession , 如果获取到, 就返回, 同一个线程里面的sqlSession 可以是同一个
  private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
  1. 如果获取不到, 就创建一个新的

  2. 执行完成后就及时关闭session, 保证每一个连接都是全新的sqlsession对象.

  3. 因为DefaultSqlSession自己做不到每次请求调用产生一个新的实例,我们干脆创建一个代理 类,也实现SqlSession,提供跟DefaultSqlSession一样的方法,在任何一个方法被调用的时候都先创建 一个DefaultSqlSession实例,再调用被代理对象的相应方法。 MyBatis还自带了一个线程安全的SqlSession实现:SqlSessionManager,实现方式一样,如果不集成 到Spring要保证线程安全,就用SqlSessionManager

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容