关于java:如何保证SqlSession的线程安全

9次阅读

共计 7791 个字符,预计需要花费 20 分钟才能阅读完成。

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 对象的,如下所示:

@Override
public SqlSession openSession(boolean autoCommit) {return sqlSessionFactory.openSession(autoCommit);
}

@Override
public SqlSession openSession(Connection connection) {return sqlSessionFactory.openSession(connection);
}

@Override
public SqlSession openSession(TransactionIsolationLevel level) {return sqlSessionFactory.openSession(level);
}

@Override
public SqlSession openSession(ExecutorType execType) {return sqlSessionFactory.openSession(execType);
}

@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {return sqlSessionFactory.openSession(execType, autoCommit);
}

@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {return sqlSessionFactory.openSession(execType, level);
}

@Override
public 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 公布!

正文完
 0