6

问题记录:


     工作环境是使用spring boot,使用用的mybatis,在一次调试中。发现每一次插入一条
 数据都会创建一个SqlSession。如图:

图1:

clipboard.png

问题可能的原因:


原因分析:#1 没有使用缓存

因为这个是插入,不是查询,所以这里不存在什么缓存的问题。

后来百度了一波,网上说是没有使用事务。

加上@Transactional


图2:

clipboard.png

发现“Creating a new SqlSession”这两个烦人的东西居然还在。

不管了,直接分析源码

直接分析源码,老子还不信了,搞不定你我还混什么:
1.开启debug
2.打上断点

图3:

clipboard.png

发现session为空

继续走...

clipboard.png

图2 #分析


public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
     //从从前线程的threadLocal 中获取sqlSessionHolder
     SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
     //调用静态方法sessionHoler 判断是否存在符合要求的sqlSession
     SqlSession session = sessionHolder(executorType, holder);
     // 判断当前sqlSessionHolder 中是否持有sqlSession (即当前操作是否在事务当中)
     if (session != null) {
         //如果持有sqlSesison 的引用,则直接获取
         return session;
     }

     if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Creating a new SqlSession");
     }
    //获取新的sqlSession 对象。这里由sessionFacory产生的defaultSqlSession
     session = sessionFactory.openSession(executorType);
    //判断判断,当前是否存在事务,将sqlSession 绑定到sqlSessionHolder 中,并放到threadLoacl 当中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
}

图3 #分析

private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
     SqlSession session = null;
     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();

         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
         }
         //返回sqlSession 
         session = holder.getSqlSession();
    }
    return session;
}

当然,这里还少了一个注册的方法,贴上:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
        SqlSessionHolder holder; 
        //判断事务是否存在
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            Environment environment =  sessionFactory.getConfiguration().getEnvironment();
            //加载环境变量,判断注册的事务管理器是否是SpringManagedTransaction,也就是Spring管理事务
        if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
             if (LOGGER.isDebugEnabled()) {
                 LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
              }

             holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 
             //如果当前回话处在事务当中,则将holder 绑定到ThreadLocal 中
             //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中 
              TransactionSynchronizationManager.bindResource(sessionFactory, holder);
              //将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations 
              TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
              //设置当前holder和当前事务同步 
              holder.setSynchronizedWithTransaction(true);
              //holder 引用次数+1
             holder.requested();
          } else {
                if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
                    }
                } else {
                       throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
                }
          }
} else {
  if (LOGGER.isDebugEnabled()) {
    LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
  }
}

}

补充


  • sqlSessionHolder 继承了spring的ResourceHolderSupport

    public abstract class ResourceHolderSupport implements ResourceHolder {
       //事务是否开启private boolean synchronizedWithTransaction = false;    
       private boolean rollbackOnly = false;
       private Date deadline;
       // 引用次数
       private int referenceCount = 0;
       private boolean isVoid = false;
    }

  • 在sqlSession 关闭session 的时候, 使用了工具了sqlSessionUtils的closeSqlSession 方法。sqlSessionHolder 也是做了判断,如果回话在事务当中,则减少引用次数,没有真实关闭session。如果回话不存在事务,则直接关闭session

    public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
         notNull(session, NO_SQL_SESSION_SPECIFIED);
         notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    
         SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
         //如果holder 中持有sqlSession 的引用,(即会话存在事务)
         if ((holder != null) && (holder.getSqlSession() == session)) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
            } 
           //每当一个sqlSession 执行完毕,则减少holder 持有引用的次数
           holder.released();
           } else {
                  if (LOGGER.isDebugEnabled()) {
                      LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
                  }
                  //如果回话中,不存在事务,则直接关闭session
                  session.close();
            }
    }

    到了这一步,问题已经很明显了。


出现原因 与 分析


  • SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory); 这一句:他从从前线程的threadLocal 中获取sqlSessionHolder。但是在在sqlSession 关闭session 的时候,sqlSessionHolder也是做了判断。如果会话在事务中,就减少引用次数,没有真实关闭session。如果会话不存在事务,则直接关闭session。也就是说,必须开启事务,但这个问题好像只是插入了一下,事务已经执行完成了,下一次插入的时候,由于上一个事务执行完成了, 如果不存在holder或没有被事务锁定,则会创建新的sqlSession,即 Creating a new SqlSession,通过sessionFactory.openSession()方法。如果会话不存在事务,就直接把session关闭了,同时,也减少了引用次数。

  • 换一句话来说:如果在插入的代码块中,再加上一个查询的代码,或者再插入一条数据的代码,这样就不会出现Creating a new SqlSession这个烦人的家伙。好了,祝大家好运!!!

引用:

SqlSessionHolder作用
如果有侵权,马上删除


smilesnake
298 声望4 粉丝