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

解决spring结合mybatis时一级缓存失效的问题

程序员文章站 2022-04-12 19:27:46
之前了解到mybatis的一级缓存是默认开启的,作用域是sqlsession,是基 hashmap的本地缓存。不同的sqlsession之间的缓存数据区域互不影响。当进行select、update、d...

之前了解到mybatis的一级缓存是默认开启的,作用域是sqlsession,是基 hashmap的本地缓存。不同的sqlsession之间的缓存数据区域互不影响。

当进行select、update、delete操作后并且commit事物到数据库之后,sqlsession中的cache自动被清空

<setting name="localcachescope" value="session"/>

结论

spring结合mybatis后,一级缓存作用:

在未开启事物的情况之下,每次查询,spring都会关闭旧的sqlsession而创建新的sqlsession,因此此时的一级缓存是没有启作用的

在开启事物的情况之下,spring使用threadlocal获取当前资源绑定同一个sqlsession,因此此时一级缓存是有效的

案例

情景一:未开启事物

@service("countryservice")
public class countryservice {

 @autowired
 private countrydao countrydao;

 // @transactional 未开启事物
 public void notransactionmethod() throws jsonprocessingexception {
  countrydo countrydo = countrydao.getbyid(1l);
  countrydo countrydo1 = countrydao.getbyid(1l);
  objectmapper objectmapper = new objectmapper();
  string json = objectmapper.writevalueasstring(countrydo);
  string json1 = objectmapper.writevalueasstring(countrydo1);
  system.out.println(json);
  system.out.println(json1);
 }
}

测试案例:

@test
public void transactiontest() throws jsonprocessingexception {
 countryservice.notransactionmethod();
}

结果:

[debug] sqlsessionutils creating a new sqlsession
[debug] springmanagedtransaction jdbc connection [com.mysql.jdbc.jdbc4connection@14a54ef6] will not be managed by spring
[debug] getbyid ==> preparing: select * from country where country_id = ?
[debug] getbyid ==> parameters: 1(long)
[debug] getbyid <==  total: 1
[debug] sqlsessionutils closing non transactional sqlsession [org.apache.ibatis.session.defaults.defaultsqlsession@3359c978]
[debug] sqlsessionutils creating a new sqlsession
[debug] sqlsessionutils sqlsession [org.apache.ibatis.session.defaults.defaultsqlsession@2aa27288] was not registered for synchronization because synchronization is not active
[debug] springmanagedtransaction jdbc connection [com.mysql.jdbc.jdbc4connection@14a54ef6] will not be managed by spring
[debug] getbyid ==> preparing: select * from country where country_id = ?
[debug] getbyid ==> parameters: 1(long)
[debug] getbyid <==  total: 1
[debug] sqlsessionutils closing non transactional sqlsession [org.apache.ibatis.session.defaults.defaultsqlsession@2aa27288]
{"countryid":1,"country":"afghanistan","lastupdate":"2006-02-15 04:44:00.0"}
{"countryid":1,"country":"afghanistan","lastupdate":"2006-02-15 04:44:00.0"}

可以看到,两次查询,都创建了新的sqlsession,并向数据库查询,此时缓存并没有起效果

情景二: 开启事物

打开@transactional注解:

@service("countryservice")
public class countryservice {

 @autowired
 private countrydao countrydao;

 @transactional
 public void notransactionmethod() throws jsonprocessingexception {
  countrydo countrydo = countrydao.getbyid(1l);
  countrydo countrydo1 = countrydao.getbyid(1l);
  objectmapper objectmapper = new objectmapper();
  string json = objectmapper.writevalueasstring(countrydo);
  string json1 = objectmapper.writevalueasstring(countrydo1);
  system.out.println(json);
  system.out.println(json1);
 }
}

使用原来的测试案例,输出结果:

[debug] sqlsessionutils creating a new sqlsession
[debug] sqlsessionutils registering transaction synchronization for sqlsession [org.apache.ibatis.session.defaults.defaultsqlsession@109f5dd8]
[debug] springmanagedtransaction jdbc connection [com.mysql.jdbc.jdbc4connection@55caeb35] will be managed by spring
[debug] getbyid ==> preparing: select * from country where country_id = ?
[debug] getbyid ==> parameters: 1(long)
[debug] getbyid <==  total: 1
[debug] sqlsessionutils releasing transactional sqlsession [org.apache.ibatis.session.defaults.defaultsqlsession@109f5dd8]
// 从当前事物中获取sqlsession
[debug] sqlsessionutils fetched sqlsession [org.apache.ibatis.session.defaults.defaultsqlsession@109f5dd8] from current transaction
[debug] sqlsessionutils releasing transactional sqlsession [org.apache.ibatis.session.defaults.defaultsqlsession@109f5dd8]
{"countryid":1,"country":"afghanistan","lastupdate":"2006-02-15 04:44:00.0"}
{"countryid":1,"country":"afghanistan","lastupdate":"2006-02-15 04:44:00.0"}

可以看到,两次查询,只创建了一次sqlsession,说明一级缓存起作用了

跟踪源码

从sqlsessiondaosupport作为路口,这个类在mybatis-spring包下,sping为sqlsession做了代理

public abstract class sqlsessiondaosupport extends daosupport {

 private sqlsession sqlsession;

 private boolean externalsqlsession;

 public void setsqlsessionfactory(sqlsessionfactory sqlsessionfactory) {
 if (!this.externalsqlsession) {
  this.sqlsession = new sqlsessiontemplate(sqlsessionfactory);
 }
 }
 //....omit
}

创建了sqlsessiontemplate后,在sqlsessiontemplate中:

public sqlsessiontemplate(sqlsessionfactory sqlsessionfactory, executortype executortype,
 persistenceexceptiontranslator exceptiontranslator) {

 notnull(sqlsessionfactory, "property 'sqlsessionfactory' is required");
 notnull(executortype, "property 'executortype' is required");

 this.sqlsessionfactory = sqlsessionfactory;
 this.executortype = executortype;
 this.exceptiontranslator = exceptiontranslator;
 //代理了sqlsession
 this.sqlsessionproxy = (sqlsession) newproxyinstance(
  sqlsessionfactory.class.getclassloader(),
  new class[] { sqlsession.class },
  new sqlsessioninterceptor());
}

再看sqlsessioninterceptor,sqlsessioninterceptor是sqlsessiontemplate的内部类:

public class sqlsessiontemplate implements sqlsession, disposablebean {
 // ...omit..
 private class sqlsessioninterceptor implements invocationhandler {
  @override
  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);
   //如果尚未开启事物(事物不是由spring来管理),则sqlsession直接提交
   if (!issqlsessiontransactional(sqlsession, sqlsessiontemplate.this.sqlsessionfactory)) {
   // force commit even on non-dirty sessions because some databases require
   // a commit/rollback before calling close()
   // 手动commit
   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 {
   //一般情况下,默认都是关闭sqlsession
   if (sqlsession != null) {
   closesqlsession(sqlsession, sqlsessiontemplate.this.sqlsessionfactory);
   }
  }
  }
 }
}

再看getsqlsession方法,这个方法是在sqlsessionutils.java中的:

public static sqlsession getsqlsession(sqlsessionfactory sessionfactory, executortype executortype, persistenceexceptiontranslator exceptiontranslator) {

 notnull(sessionfactory, no_sql_session_factory_specified);
 notnull(executortype, no_executor_type_specified);
 //获取holder
 sqlsessionholder holder = (sqlsessionholder) transactionsynchronizationmanager.getresource(sessionfactory);
 //从sessionholder中获取sqlsession
 sqlsession session = sessionholder(executortype, holder);
 if (session != null) {
 return session;
 }

 if (logger.isdebugenabled()) {
 logger.debug("creating a new sqlsession");
 }

 //如果sqlsession不存在,则创建一个新的
 session = sessionfactory.opensession(executortype);
 //将sqlsession注册在sessionholder中
 registersessionholder(sessionfactory, executortype, exceptiontranslator, session);

 return session;
}

private static void registersessionholder(sqlsessionfactory sessionfactory, executortype executortype,
  persistenceexceptiontranslator exceptiontranslator, sqlsession session) {
 sqlsessionholder holder;
 //在开启事物的情况下
 if (transactionsynchronizationmanager.issynchronizationactive()) {
  environment environment = sessionfactory.getconfiguration().getenvironment();

  //由spring来管理事物的情况下
  if (environment.gettransactionfactory() instanceof springmanagedtransactionfactory) {
  if (logger.isdebugenabled()) {
   logger.debug("registering transaction synchronization for sqlsession [" + session + "]");
  }

  holder = new sqlsessionholder(session, executortype, exceptiontranslator);
  //将sessionfactory绑定在sessionholde相互绑定
  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");
  }
 }

再看transactionsynchronizationmanager.bindresource的方法:

public abstract class transactionsynchronizationmanager {

 //omit...
 private static final threadlocal<map<object, object>> resources =
   new namedthreadlocal<map<object, object>>("transactional resources");

  // key:sessionfactory, value:sqlsessionholder(connection)
  public static void bindresource(object key, object value) throws illegalstateexception {
  object actualkey = transactionsynchronizationutils.unwrapresourceifnecessary(key);
  assert.notnull(value, "value must not be null");
  //从threadlocal类型的resources中获取与当前线程绑定的资源,如sessionfactory,connection等等
  map<object, object> map = resources.get();
  // set threadlocal map if none found
  if (map == null) {
   map = new hashmap<object, object>();
   resources.set(map);
  }
  object oldvalue = map.put(actualkey, value);
  // transparently suppress a resourceholder that was marked as void...
  if (oldvalue instanceof resourceholder && ((resourceholder) oldvalue).isvoid()) {
   oldvalue = null;
  }
  if (oldvalue != null) {
   throw new illegalstateexception("already value [" + oldvalue + "] for key [" +
    actualkey + "] bound to thread [" + thread.currentthread().getname() + "]");
  }
  if (logger.istraceenabled()) {
   logger.trace("bound value [" + value + "] for key [" + actualkey + "] to thread [" +
    thread.currentthread().getname() + "]");
  }
  }
}

这里可以看到,spring是如何做到获取到的是同一个sqlsession,前面的长篇大论,就是为使用threadlocal将当前线程绑定创建sqlsession相关的资源,从而获取同一个sqlsession

以上这篇解决spring结合mybatis时一级缓存失效的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。