欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-07 16:42:06
因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存。 习惯是本地缓存自己用map写或者引入第三方的本地缓存框架ehcache,gua...

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

以上所述是小编给大家介绍的spring 整合mybatis后用不上session缓存的原因分析,希望对大家有所帮助