转载:spring 整合mybatis后用不上session缓存的原因分析

因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存。 习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,Guava

所以提出来纠结下

实验下(spring整合mybatis略,网上一堆),先看看mybatis级别的session的缓存

放出打印sql语句

configuration.xml 加入

?
1
2
3
4
    
     "logImpl" value= "STDOUT_LOGGING" />
  

测试源代码如下:

dao类 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
  * 测试spring里的mybatis为啥用不上缓存
  *
  * @author 何锦彬 2017.02.15
  */
@Component
public class TestDao {
   private Logger logger = Logger.getLogger(TestDao. class .getName());
   @Autowired
   private SqlSessionTemplate sqlSessionTemplate;
   @Autowired
   private SqlSessionFactory sqlSessionFactory;
   /**
    * 两次SQL
    *
    * @param id
    * @return
    */
   public TestDto selectBySpring(String id) {
     TestDto testDto = (TestDto) sqlSessionTemplate.selectOne( "com.hejb.TestDto.selectByPrimaryKey" , id);
     testDto = (TestDto) sqlSessionTemplate.selectOne( "com.hejb.TestDto.selectByPrimaryKey" , id);
     return testDto;
   }
   /**
    * 一次SQL
    *
    * @param id
    * @return
    */
   public TestDto selectByMybatis(String id) {
     SqlSession session = sqlSessionFactory.openSession();
     TestDto testDto = session.selectOne( "com.hejb.TestDto.selectByPrimaryKey" , id);
     testDto = session.selectOne( "com.hejb.TestDto.selectByPrimaryKey" , id);
     return testDto;
   }
}

测试service类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
public class TestService {
   @Autowired
   private TestDao testDao;
   /**
    * 未开启事务的spring Mybatis查询
    */
   public void testSpringCashe() {
     //查询了两次SQL
     testDao.selectBySpring( "1" );
   }
   /**
    * 开启事务的spring Mybatis查询
    */
   @Transactional
   public void testSpringCasheWithTran() {
     //spring开启事务后,查询1次SQL
     testDao.selectBySpring( "1" );
   }
   /**
    * mybatis查询
    */
   public void testCash4Mybatise() {
     //原生态mybatis,查询了1次SQL
     testDao.selectByMybatis( "1" );
   }
}

输出结果:

testSpringCashe()方法执行了两次SQL, 其它都是一次

源码追踪:

先看mybatis里的sqlSession

跟踪到最后 调用到 org.apache.ibatis.executor.BaseExecutor的query方法

?
1
2
3
4
5
6
7
8
try {
    queryStack++;
    list = resultHandler == null ? (List) localCache.getObject(key) : null ; //先从缓存中取
    if (list != null ) {
     handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); //注意里面的key是CacheKey
    } else {
     list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

贴下是怎么取出缓存数据的代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
   if (ms.getStatementType() == StatementType.CALLABLE) {
    final Object cachedParameter = localOutputParameterCache.getObject(key); //从localOutputParameterCache取出缓存对象
    if (cachedParameter != null && parameter != null ) {
     final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
     final MetaObject metaParameter = configuration.newMetaObject(parameter);
     for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
      if (parameterMapping.getMode() != ParameterMode.IN) {
       final String parameterName = parameterMapping.getProperty();
       final Object cachedValue = metaCachedParameter.getValue(parameterName);
       metaParameter.setValue(parameterName, cachedValue);
      }
     }
    }
   }
  }

 

发现就是从localOutputParameterCache就是一个PerpetualCache, PerpetualCache维护了个map,就是session的缓存本质了。

重点可以关注下面两个累的逻辑

PerpetualCache , 两个参数, id和map

CacheKey,map中存的key,它有覆盖equas方法,当获取缓存时调用.

这种本地map缓存获取对象的缺点,就我踩坑经验(以前我也用map去实现的本地缓存),就是获取的对象非clone的,返回的两个对象都是一个地址

而在spring中一般都是用sqlSessionTemplate,如下

?
1
2
3
4
5
6
7
8
9
10
11
12
"sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean" >
     "dataSource" ref= "dataSource" />
     "configLocation" value= "classpath:configuration.xml" />
     "mapperLocations" >
      
         classpath*:com/hejb/sqlmap/*.xml
      
    
  
   "sqlSessionTemplate" class = "org.mybatis.spring.SqlSessionTemplate" >
     "sqlSessionFactory" />
  

在SqlSessionTemplate中执行SQL的session都是通过sqlSessionProxy来,sqlSessionProxy的生成在构造函数中赋值,如下:

?
1
2
3
4
this .sqlSessionProxy = (SqlSession) newProxyInstance(
     SqlSessionFactory. class .getClassLoader(),
     new Class[] { SqlSession. class },
     new SqlSessionInterceptor());

sqlSessionProxy通过JDK的动态代理方法生成的一个代理类,主要逻辑在InvocationHandler对执行的方法进行了前后拦截,主要逻辑在invoke中,包好了每次执行对sqlsesstion的创建,common,关闭

代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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);
     }
    }
   }
  }

因为每次都进行创建,所以就用不上sqlSession的缓存了.

对于开启了事务为什么可以用上呢, 跟入getSqlSession方法

如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
   notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
   notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
   SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
   // 首先从SqlSessionHolder里取出session
   SqlSession session = sessionHolder(executorType, holder);
   if (session != null ) {
    return session;
   }
   if (LOGGER.isDebugEnabled()) {
    LOGGER.debug( "Creating a new SqlSession" );
   }
   session = sessionFactory.openSession(executorType);
   registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
   return session;
  }

在里面维护了个SqlSessionHolder,关联了事务与session,如果存在则直接取出,否则则新建个session,所以在有事务的里,每个session都是同一个,故能用上缓存了

以上所述是小编给大家介绍的spring 整合mybatis后用不上session缓存的原因分析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

来自:http://www.jb51.net/article/105791.htm


补充:  private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      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);
        }
      }
    }
  }

理解:有事物则不提交,没有事物则提交


public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {


    notNull(sessionFactory, "No SqlSessionFactory specified");
    notNull(executorType, "No ExecutorType specified");


    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);


    if (holder != null && holder.isSynchronizedWithTransaction()) {
      if (holder.getExecutorType() != executorType) {
        throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
      }


      holder.requested();


      if (logger.isDebugEnabled()) {
        logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
      }


      return holder.getSqlSession();
    }


    if (logger.isDebugEnabled()) {
      logger.debug("Creating a new SqlSession");
    }


    SqlSession session = sessionFactory.openSession(executorType);


    // Register session holder if synchronization is active (i.e. a Spring TX is active)
    //
    // Note: The DataSource used by the Environment should be synchronized with the
    // transaction either through DataSourceTxMgr or another tx synchronization.
    // Further assume that if an exception is thrown, whatever started the transaction will
    // handle closing / rolling back the Connection associated with the SqlSession.
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();


      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        if (logger.isDebugEnabled()) {
          logger.debug("Registering transaction synchronization for SqlSession [" + session + "]");
        }


        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        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");
      }
    }

在同一事物里放回相同sqlsession,不同事物则新建sqlsession

你可能感兴趣的:(mybatis)