当前位置: 移动技术网 > IT编程>开发语言>Java > spring 整合mybatis后用不上session缓存的原因分析

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

2019年07月22日  | 移动技术网IT编程  | 我要评论

因为一直用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缓存的原因分析,希望对大家有所帮助

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网