zoukankan      html  css  js  c++  java
  • sqlsession、sqlsessionManager以及sqlsessionTemplate的理解

    sqlSession

    是mybatis的核心操作类,其中对数据库的crud都封装在这个中,是一个顶级接口,其中默认实现类是DefaultSqlSession这个类,

    为什么说DefaultSqlsession不是线程安全的?

    首先我们都知道mybatis在底层都是使用的JDBC,而JDBC这本来就是线程不安全的(连接对象Connection只有一个),所以我们只要关注session和connnect的关系就好了

    首先是一段最普通的mybatis生成sqlSession的代码:

    SqlSession session = null;
    String resource = "configuration.xml";
    // 使用io流读取配置
    InputStream inputStream;
    inputStream = Resources.getResourceAsStream(resource);
    //这里是解析配置文件
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 得到了一个会话,有了这个会话,你就可以对数据进行增,删,改,查的操作
    session = sqlSessionFactory.openSession();
    

    在此时sqlsessionFactory打开(创建了一个sqlsession会话),下面我们来看看这个session是怎么产生的:

    //这个是org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
    @Override
      public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }
    //之后走到该类的这个方法里来(openSessionFromDataSource)
      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来执行数据库操作是线程不安全的

    sqlsessionTemplate

    为什么说sqlsessionTemplate是线程安全的?

      public class SqlSessionTemplate implements SqlSession, DisposableBean {
    
      private final SqlSessionFactory sqlSessionFactory;
    
      private final ExecutorType executorType;
    
      private final SqlSession sqlSessionProxy;
    
      private final PersistenceExceptionTranslator exceptionTranslator;
    
      private final PersistenceExceptionTranslator exceptionTranslator;
      
      public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
      }
      ..........
          
      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());
        }
      }
    

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

    sqlSessionManager

    首先我们来看看这个类实现的接口

    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 SqlSessionInterceptor());
      }
      .....
      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));
      }
    }
    

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

    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();
        }
    }
    

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

    从这个方法我们可以看出每个线程都会被分配一个对应的对象副本,而且这个是保证了Connection对象线程的安全性,下面我们来看看具体执行是什么样子的:

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionManager.SqlSessionInterceptor());
    }
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
    }
    
    public <T> Cursor<T> selectCursor(String statement) {
       return this.sqlSessionProxy.selectCursor(statement);
    }
    
    public <T> Cursor<T> selectCursor(String statement, Object parameter) {
        return this.sqlSessionProxy.selectCursor(statement, parameter);
    }
    

    眼熟吧,标准的代理模式,和sqlsessionTemplate类似,具体操作数据库的都是通过产生的动态代理对象去执行的。

    测试

    @Component
    public class TestSqlSessionManager {
    
        @Autowired
        private SqlSessionTemplate sqlSessionTemplate;
    //    @Autowired
    //    private SqlSessionFactory sqlSessionFactory;//这个是一个接口,别想多了你
        @Autowired
        private SqlSessionManager sqlSessionManager;
    
        private static ApplicationContext context;
    
        private static String namespace = "com.swjd.dao.DashboardDao";
    
        static {
            TestSqlSessionManager.context = new ClassPathXmlApplicationContext(
                    "spring/applicationContext-dao.xml","spring/applicationContext-service.xml",
                    "spring/applicaitonContext-transaction.xml");
        }
    
        @Test
        public void bTest(){
            //三个类
            SqlSessionTemplate sqlSessionTemplate = context.getBean(SqlSessionTemplate.class);
            SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
            SqlSessionManager sqlSessionManager = context.getBean(SqlSessionManager.class);
                List<Object> list = sqlSessionTemplate.selectList(namespace + ".selectByExample");
    //            SqlSession sqlSession = sqlSessionManager.openSession();
    //            List<Object> objects = sqlSession.selectList(namespace + ".selectByExample");
                //使用多线程测试
                //测试sqlsessionFactory
                for (int i=0;i<4;i++){
                    int index = i;
                    Thread thread=new Thread(() ->{
                        try {
                            Class<? extends SqlSessionTemplate> aClass = sqlSessionTemplate.getClass();
                            Field sqlSessionProxy = aClass.getDeclaredField("sqlSessionProxy");
                            sqlSessionProxy.setAccessible(true);
                            Object o1 = sqlSessionProxy.get(sqlSessionTemplate);
                            System.out.println("A第"+ index +"个===================="+o1.hashCode());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        List<Object> objects = sqlSessionTemplate.selectList(namespace + ".selectByExample");
                    });
                    thread.start();
                }
                //测试sqlsessionManager
                for (int i=0;i<4;i++){
                    int index = i;
                    Thread thread=new Thread(() ->{
                        SqlSession sqlSession = sqlSessionManager.openSession();
                        try {
                            Class<? extends SqlSessionManager> aClass = sqlSessionManager.getClass();
                            Field sqlSessionProxy = aClass.getDeclaredField("sqlSessionProxy");
                            sqlSessionProxy.setAccessible(true);
                            Object o2 = sqlSessionProxy.get(sqlSessionManager);
                            System.out.println("B第"+ index +"个===================="+o2.hashCode());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        List<Object> objects = sqlSessionManager.selectList(namespace + ".selectByExample");
                    });
                    thread.start();
                }
        }
    }
    

    测试结果如图:

    总结

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

    疑惑

    JDK动态代理创建的对象会占用内存吗?如果会占用内存的话,那么创建动态代理对象也会是一个吃内存的操作,那么在内存方面,sqlsessionTemplate和sqlsessionManager都会是特别不友好的。

  • 相关阅读:
    二、项目和框架矩阵
    一、PowerDesigner概述(系统分析与建模)
    Visual Studio Code 常用插件整理
    IntelliJ IDEA 显示行号
    IntelliJ IDEA 常用快捷键
    MyEclipse中常用的快捷键
    使用Oracle数据库,对某个表频繁更新
    更改MyEclipse中的src目录的浏览方式
    nginx最大并发连接数的思考:worker_processes、worker_connections、worker_rlimit_nofile
    Nginx性能优化
  • 原文地址:https://www.cnblogs.com/five-five/p/14083948.html
Copyright © 2011-2022 走看看