1 SqlSession

SQL Server asp多线程 sqlsession线程安全_安全


sqlSessionFactoryBean,主要作用是通过getObject得到sqlSessionFactory,同时可以设置数据源,mybatis基本配置等。

sqlSessionFactory,用于创建sqlSession的工厂方法。

sqlSession,执行sql命令的会话。

MapperFactoryBean,创建mapper的工厂类,getObject()得到mapper接口的动态代理生成的代理类,它继承SqlSessionDaoSupport来间接操作SqlSessionTemplate。即getObject()->getMapper()

@MapperScan,会扫描目标目录的所有Mapper接口,并名字定义成mapper接口名,但是class类名是MapperFactoryBean,里面的有一个属性是mapper的权限名。如此启动项目,加载单例bean到上下文容器的时候,调用getObject会调用SqlSessionTemplate的getMapper(),得到代理mapper对象。

@Mapper,若没有使用@MapperScan【@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })】,而使用@Mapper,原理也类似,会扫描主启动类目录下的所有标注了@Mapper的mapper接口文件

1.2 生命周期

对象

作用域

说明

SqlSessionFactoryBuilder

方法局部(method)

工厂模式,创建后就不再需要了,因为就是为了创造SqlSessionFactory的工具类

SqlSessionFactory

应用级别(application)

连接池。创建就一直存在,程序运行就创建,程序关闭才释放

SqlSession

请求和操作

类似JDBC的一个Connection对象。因此每次使用完就应该要关闭,这样才能回收到SqlSessionFactory中继续利用,而且SqlSession不是线程安全的,不能被共享。

Mapper

方法

在编程式的开发中,SqlSession 我们会在每次请求的时候创建一个,但是Spring里面只有一个SqlSessionTemplate(默认是单例的),多个线程同时调用的时候怎么保证线程安全?

1.2 SqlSession接口的实现类

SQL Server asp多线程 sqlsession线程安全_SQL Server asp多线程_02

1.2.1 SqlSessionTemplate (为什么线程安全)

SQL Server asp多线程 sqlsession线程安全_SQL Server asp多线程_03

从这个构造方法可以看出,sqlsessionTemplate传参是必须需要一个sqlsessionfactory的,sqlsessionTemplate在执行crud操作时,都不是通过唯一的一个sqlsession来执行的,他都是通过动态代理来执行具体的操作的,所以多个线程持有同一个sqlsessionTemplate是不会产生线程安全问题的。

SqlSessionTemplate是sqlSession的实现类,是线程安全的。里面有一个动态代理的SqlSession sqlSessionProxy;代理sqlSessionProxy执行invoke方法的时候,每次invoke方法都是新生成一个SqlSession来执行,这样就保持了线程安全。
还有一个点,getMapper()方法是得到一个mapper接口的代理对象,且会传入this即本sqlSessionTemplate,最终调用sql语句的时候还是使用代理的sqlSessionProxy(实际还是invoke的新的SqlSession)。【2个动态代理】

//获取代理mapper对象,且一直传入this即本sqlSessionTemplate对象,执行sql语句的时候使用的代理sqlSessionProxy,
//它又使用invoke方法的新的sqlSession
		public <T> T getMapper(Class<T> type) {
        return this.getConfiguration().getMapper(type, this);
    }

SqlSessionTemplate 里面有DefaultSqlSession 的所有的方法:selectOne()、selectList()、insert()、update()、delete(),不过它都是通过一个代理对象实现的。这个代理对象在构造方法里面通过一个代理类创建:

this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), 
										new Class[]{SqlSession.class}, 
										new SqlSessionTemplate.SqlSessionInterceptor());

所有的方法都会先走到内部代理类SqlSessionInterceptor 的invoke()方法:

/**	代理需要将MyBatis方法调用路由到从Spring的事务管理器获得的适当SqlSession
   * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
   * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
   * {@code PersistenceExceptionTranslator}.
   */
   //SqlSessionInterceptor内部代理类
  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    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 {
        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);
        }
      }
    }
  }

从Spring事务管理器中得到一个SqlSession,如果需要创建一个新的。

SQL Server asp多线程 sqlsession线程安全_安全_04

从当前事务之外得到一个SqlSession,如果没有就创造一个新的。然后,如果事务被打开,且事务管理器是SpringManagedTransactionFactory时,将得到的SqlSession同当前事务同步

SQL Server asp多线程 sqlsession线程安全_java_05

1.2.2 SqlSessionManager

SqlSessionManager是sqlSession的实现类,是线程安全的。里面也有一个动态代理的SqlSession sqlSessionProxy;同时维护一个ThreadLocal localSqlSession,所以代理sqlSessionProxy执行invoke方法的时候,就是拿各自线程的ThreadLocal的SqlSession。

public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    private final SqlSessionFactory sqlSessionFactory;
    private final SqlSession sqlSessionProxy;
    private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal();

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        		 new Class[]{SqlSession.class}, 
        		 new SqlSessionManager.SqlSessionInterceptor());
    }

可以看出他的一个必要的参数也是sqlsessionFactory,SqlSessionManager既实现了SqlSessionFactory,也实现了SqlSession,具备生产SqlSession的能力,也具备SqlSession的能力,SqlSession的作用是执行具体的Sql语句

localSqlSession,这个属性其实就是一个ThreadLocal类,可以为每一个线程分配一个副本对象,来保证线程安全。

SQL Server asp多线程 sqlsession线程安全_spring_06

sqlsessionManager他把构造方法私有化了,想要创建一个sqlsessionManager对象,你只能调用newInstance()来创建一个SqlsessionManager对象,下面来看连接对象Connection他是怎么获取的

public Connection getConnection() {
    SqlSession sqlSession = (SqlSession)this.localSqlSession.get();
    if (sqlSession == null) {
        throw new SqlSessionException("Error:  Cannot get connection.  No managed session is started.");
    } else {
        return sqlSession.getConnection();
    }
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
            if (sqlSession != null) {
                try {
                    return method.invoke(sqlSession, args);
                } catch (Throwable var19) {
                    throw ExceptionUtil.unwrapThrowable(var19);
                }
            } else {
                SqlSession autoSqlSession = SqlSessionManager.this.openSession();
                Throwable var6 = null;

                Object var8;
                try {
                    try {
                        Object result = method.invoke(autoSqlSession, args);
                        autoSqlSession.commit();
                        var8 = result;
                    } catch (Throwable var20) {
                        autoSqlSession.rollback();
                        throw ExceptionUtil.unwrapThrowable(var20);
                    }
                } catch (Throwable var21) {
                    var6 = var21;
                    throw var21;
                } finally {
                    if (autoSqlSession != null) {
                        if (var6 != null) {
                            try {
                                autoSqlSession.close();
                            } catch (Throwable var18) {
                                var6.addSuppressed(var18);
                            }
                        } else {
                            autoSqlSession.close();
                        }
                    }

                }

                return var8;
            }
        }

DefaultSqlSession(线程不安全)

DefaultSqlSession是sqlSession的实现类,普通的一次请求,不安全的。

SQL Server asp多线程 sqlsession线程安全_安全_07

mybatis在底层都是使用的JDBC,而JDBC这本来就是线程不安全的(连接对象Connection只有一个)

SQL Server asp多线程 sqlsession线程安全_java_08

//之后走到该类的这个方法里
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      //开始创建事物
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //将事物传递给执行器Executor,这个是session执行数据库操作的核心(有三种执行器类型)
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

最后sqlsession执行都是通过执行器执行的,默认执行器是SimpleExecutor,她通过连接Connection这个类创建了Statement这个JDBC要用到的对象,开始走JDBC的流程:

//查询方法
@Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 这里创建statement对象,这个方法中就用到了Connection连接对象,此时我们主要看这个方法中Connection的创建时怎么样的
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

prepareStatement(handler, ms.getStatementLog())方法解析(重点看Connection他是怎么拿的)

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 看下面的方法,此时只需要看这个方法
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
  //由这个方法可以看出,具体实现是transaction.getConnection();
  protected Connection getConnection(Log statementLog) throws SQLException {
    // 这里最终同通过创建Executor时传入的transcation进行了连接获取
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

transaction.getConnection();

//可以看出他只会产生一个连接 
@Override
  public Connection getConnection() throws SQLException {
    // 这里只要有连接了就不重新打开连接了(从数据源中再次获取),说明只能有一个连接在一个org.apache.ibatis.transaction.Transaction中
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

最终可以看出一次SqlSession的执行最终只会产生一个connection,所以我们设想一下,在两个线程通过同一个sqlsession来执行crud,那么就有可能,我先跑完的线程,把唯一的这一个连接给关闭掉,从而造成另一条线程的逻辑不被成功执行,所以通过DefaultSqlSession来执行数据库操作是线程不安全的。

1.3

  1. DefaultSqlSession的内部没有提供像SqlSessionManager一样通过ThreadLocal的方式来保证线程的安全性;
  2. SqlSessionManager是通过localSqlSession这个ThreadLocal变量,记录与当前线程绑定的SqlSession对象,供当前线程循环使用,从而避免在同一个线程多次创建SqlSession对象造成的性能损耗;
  3. DefaultSqlSession不是线程安全的,我们在进行原生开发的时候想要达到线程安全的话,那就需要每次为一个操作都创建一个SqlSession对象,其性能可想而知