zoukankan      html  css  js  c++  java
  • 分析下为什么spring 整合mybatis后为啥用不上session缓存

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

    所以提出来纠结下

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

    放出打印sql语句

    configuration.xml 加入

    <settings>
            <!-- 打印查询语句 -->
            <setting name="logImpl" value="STDOUT_LOGGING" />
        </settings>

    测试源代码如下:

    dao类

    /**
     * 测试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类

    @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方法

     try {
          queryStack++;
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; //先从缓存中取
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); //注意里面的key是CacheKey
          } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
    

      

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

    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,如下

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="configLocation" value="classpath:configuration.xml" />
            <property name="mapperLocations">
                <list>
                    <value>classpath*:com/hejb/sqlmap/*.xml</value>
                </list>
            </property>
        </bean>
        <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
            <constructor-arg ref="sqlSessionFactory" />
        </bean>
     

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

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

      

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

    代码如下:

     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方法

    如下:

     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都是同一个,故能用上缓存了

    留下个下小思考

    ,CacheKey是怎么作为KEY来判断是否执行的是同一条SQL与参数的呢

  • 相关阅读:
    MongoDB +JSON+JQuery.Pagination+Linq 实现无刷新分页
    DBHelper
    C# .Net动态调用webService
    .net 将图片文件转换成流输出到浏览器
    将mongodb作为服务
    .net 最简单文件上传支持跨服务器
    Windows Phone 7
    javascript中对Date类型的常用操作
    DataTable 转换JSON
    C# 实现 MemCache 监控管理工具
  • 原文地址:https://www.cnblogs.com/springsource/p/6404615.html
Copyright © 2011-2022 走看看