首页 > 开发 > Java > 正文

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

2024-07-13 10:04:06
字体:
来源:转载
供稿:网友

因为一直用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 */@Componentpublic 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类

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

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


注:相关教程知识阅读请移步到JAVA教程频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表