mybatis 中 sqlSession = factory.openSession(); sqlSession是什么?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis 中 sqlSession = factory.openSession(); sqlSession是什么?相关的知识,希望对你有一定的参考价值。

问题一:sqlSession到底是个什么东西,用sqlSession.selectOne(),执行查询比传统的jdbc减少了哪些步骤?
SqlSession sqlSession = null;
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = factory.openSession();

问题二:用传统jdbc的时候,可以通过Druid获取连接对象,然后把这个对象放到ThreadLocal中。要用的时候,可以从ThreadLocal中返回一个连接副本,再通过这个连接对象执行具体的sql语句。prep = 连接对象.prepareStatement(sql); 但是用mybatis时,怎么使用数据库连接池和ThreadLocal?? 可以把sqlSession放到ThreadLocal中吗?如果这样,连接池岂不是没用了?

问题三:以上的说明我的理解是否有错?

新手求救各路大神,跪谢了
答案能解决问题,会追加积分的。

1、这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。

2、因此 SqlSessionFactoryBuilder 实例的最佳范围是方法范围(也就是局部方法变量)。

3、你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,
但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情。

4、SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。

5、使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,
多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。

6、因此  SqlSessionFactory 的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
参考技术A 创建会话工厂

Mybatis 中sqlsession源码解析

一、sqlsession获取过程

1、基础配置

  在mybatis框架下进行的数据库操作都需要首先获取sqlsession,在mybatis与spring集成后获取sqlsession需要用到sqlsessionTemplate这个类。

首先在spring对sqlsessionTemplate进行配置,使用到的是 org.mybatis.spring.SqlSessionTemplate 这个类。

<!-- SqlSession实例 -->
<bean id="sessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"
        destroy-method="close">
<!--当构造函数有多个参数时,可以使用constructor-arg标签的index属性,index属性的值从0开始,这里将sqlsessionFactory作为第一个参数传入--> 
<constructor-arg index="0" ref="sqlSessionFactory" /> 
</bean>
<!-- 将数据源映射到sqlSessionFactory中 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
    <property name="dataSource" ref="dataSource" />
</bean>

所以在sqlsessionTemplate的初始化过程中,首先会将sqlsessionFactory作为参数传入,sqlsessionFactory中映射了数据源信息。

配置事务,在后来的sqlsession获取过程中会对事务进行判断

<!--======= 事务配置 Begin ================= -->
<!-- 事务管理器(由Spring管理MyBatis的事务) -->
<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 关联数据源 -->
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!--======= 事务配置 End =================== -->

2、sqlsessionTemplate的初始化

public class SqlSessionTemplate implements SqlSession {
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property \'sqlSessionFactory\' is required");
        Assert.notNull(executorType, "Property \'executorType\' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }

  SqlsessionTemplate类的最开始初始化过程中,首先会通过sqlsessionFactory参数进行构造,通过Proxy.newProxyInstance()方法来创建代理类,表示创建SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlsessionTemplate的一个内部类SqlSessionInterceptor的invoke方法,最终初始化sqlsessionProxy。

3、sqlsession的调用过程

由于上面两个过程中已经将sqlsessionTemplate中的sqlsessionProxy已经初始化完毕,所以在代码中可以进行调用。调用最终都会进入SqlSessionInterceptor的invoke方法。

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的)
      //这个方法可以根据Spring的事物上下文来获取事物范围内的sqlSession
      final SqlSession sqlSession = SqlSessionUtils.getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //调用真实SqlSession的方法
        Object result = method.invoke(sqlSession, args);
        //然后判断一下当前的sqlSession是否有配置Spring事务 如果没有自动commit
        if (!SqlSessionUtils.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) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        //关闭sqlSession
        //它会根据当前的sqlSession是否在Spring的事务上下文当中来执行具体的关闭动作
        //如果sqlSession被Spring事务管理 则调用holder.released(); 使计数器-1
        //否则才真正的关闭sqlSession
        SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }

在上面的代码中用到两个很关键的方法:

  获取sqlsession方法:SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

  关闭sqlsession方法:SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {     
    //根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,当sqlSessionFactory创建了sqlSession,就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式 
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 
 //如果holder不为空,且和当前事务同步 
    if (holder != null && holder.isSynchronizedWithTransaction()) { 
      //hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,原因就是同一个事务中同一个sqlSessionFactory创建的sqlSession会被重用 
      if (holder.getExecutorType() != executorType) { 
        throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); 
      } 
      //增加该holder,也就是同一事务中同一个sqlSessionFactory创建的唯一sqlSession,其引用数增加,被使用的次数增加 
      holder.requested(); 
   //返回sqlSession 
      return holder.getSqlSession(); 
    } 
 //如果找不到,则根据执行类型构造一个新的sqlSession 
    SqlSession session = sessionFactory.openSession(executorType); 
 //判断同步是否激活,只要SpringTX被激活,就是true 
    if (isSynchronizationActive()) { 
   //加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务 
      Environment environment = sessionFactory.getConfiguration().getEnvironment(); 
      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { 
  //如果是,则将sqlSession加载进事务管理的本地线程缓存中 
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 
  //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中 
        bindResource(sessionFactory, holder); 
  //将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations 
        registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); 
        //设置当前holder和当前事务同步 
  holder.setSynchronizedWithTransaction(true); 
  //增加引用数 
        holder.requested(); 
      } else { 
        if (getResource(environment.getDataSource()) == null) { 
        } else { 
          throw new TransientDataAccessResourceException( 
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); 
        } 
      } 
    } else { 
    } 
    return session; 
  }
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { 
 //其实下面就是判断session是否被Spring事务管理,如果管理就会得到holder  
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 
    if ((holder != null) && (holder.getSqlSession() == session)) { 
   //这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用 
      holder.released(); 
    } else { 
   //如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close 
      session.close(); 
    } 
  }

二、mybatis的缓存

1、一级缓存

  Mybatis的一级缓存是默认开启的,主要是通过sqlsession来实现的,每个sqlsession对象会在本地创建一个缓存(local cache),对于每次查询都会在本地缓存中进行查询,如果命中则直接返回,如果没查到则进入到数据库中进行查找。一级缓存是sqlsession级别的。

  从上面sqlsession的获取源码中可以看到,每次获取一个全新的sqlsession最终都是会保存在ThreadLocal中跟线程绑定,如果在spring中配置了事务则整个事务周期里面都共享一个sqlsession,如果没有配置事务则每次请求都是一个独立的sqlsession。每次执行完后数据库操作后,如果还在事务周期中只对sqlsession的引用次数减一,否则直接关闭sqlsession。

一级缓存执行的时序图:

 

小结:

MyBatis一级缓存的生命周期和SqlSession一致。
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

2、二级缓存

在系统中如果需要使用二级缓存则直接在spring中进行配置声明即可。

<!-- 这个配置使全局的映射器启用或禁用 缓存 -->
<setting name="cacheEnabled" value="true" />
<!-- 开启二级缓存开关 -->
<cache/>

  MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis,Memcached等分布式缓存可能成本更低,安全性也更高。

 

以上是关于mybatis 中 sqlSession = factory.openSession(); sqlSession是什么?的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis源码解读——SqlSession(上)

mybatis中的factory工厂与Sqlsession

使用ThreadLocal管理Mybatis中SqlSession对象

关于MyBatis sqlSession的一点整理

MyBatis之SqlSessions

SSM-MyBatis-08:Mybatis中SqlSession的commit方法为什么会造成事物的提交