ASP源码.NET源码PHP源码JSP源码JAVA源码DELPHI源码PB源码VC源码VB源码Android源码
当前位置:首页 >> 低调看直播体育app软件下载 >> 移动开发 >> mybatis缓存的使用及理解

mybatis缓存的使用及理解

来源:网络整理     时间:2016-04-09     关键词:mybatis

本篇文章主要介绍了"mybatis缓存的使用及理解",主要涉及到mybatis方面的内容,对于移动开发感兴趣的同学可以参考一下: 和hibernate一样,mybatis也有缓存机制 一级缓存是基于 PerpetualCache(mybatis自带)的 HashMap 本地缓存,作用范围为...

和hibernate一样,mybatis也有缓存机制
一级缓存是基于 PerpetualCache(mybatis自带)的 HashMap 本地缓存,作用范围为session,所以当session commit或close后,缓存就会被清空
二级缓存默认也是基于 PerpetualCache,但是可以为其制定存储源,比如ehcache
一级缓存缓存的是SQL语句,而二级缓存缓存的是结果对象,看如下例子(mybatis的日志级别设为debug)
?
1
2
3
4
5
6
7
8
List users = sqlSession.selectList("com.my.mapper.UserMapper.getUser", "jack");
System.out.println(users);
 
//sqlSession.commit();①
 
List users2 = sqlSession.selectList("com.my.mapper.UserMapper.getUser", "jack");//②admin
System.out.println(users);
结果是只发起一次SQL语句,如果我们把②出的参数jack改为admin,发现还是只发起一次SQL语句,但是会设置不同参数
如果把①处去掉注释,会发现不会有缓存了
下面就来启用二级缓存
在配置文件中启用二级缓存
?
1

在需要进行缓存的mapper文件UserMapper.xml中加上
?
1
   -->

用上面那个会输出更加详细的日志,下面的不会
需要用到ehcache.jar,下载地址:http://sourceforge.net/projects/ehcache/files/ehcache/ehcache-2.7.0/ehcache-2.7.0-distribution.tar.gz/download
mybatis-ehcache.jar下载地址:http://code.google.com/p/mybatis/downloads/detail?name=mybatis-ehcache-1.0.2-SNAPSHOT-bundle.zip&can=3&q=Product%3DCache
--尽量避免使用二级缓存技术,多表操作 的业务场景下
一、创建Cache的完整过程
我们从SqlSessionFactoryBuilder解析mybatis-config.xml配置文件开始:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
然后是:
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
看parser.parse()方法:
parseConfiguration(parser.evalNode("/configuration"));
看处理Mapper.xml文件的位置:
mapperElement(root.evalNode("mappers"));
看处理Mapper.xml的XMLMapperBuilder:
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration,
                    resource, configuration.getSqlFragments());
mapperParser.parse();
继续看parse方法:
configurationElement(parser.evalNode("/mapper"));
到这里:
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
     throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
从这里看到namespace就是xml中元素的属性。然后下面是先后处理的cache-ref和cache,后面的cache会覆盖前面的cache-ref,但是如果一开始cache-ref没有找到引用的cache,他就不会被覆盖,会一直到最后处理完成为止,最后如果存在cache,反而会被cache-ref覆盖。这里是不是看着有点晕、有点乱?所以千万别同时配置这两个,实际上也很少有人会这么做。
看看MyBatis如何处理
private void cacheElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type", "PERPETUAL");
        Class typeClass = typeAliasRegistry.resolveAlias(type);
        String eviction = context.getStringAttribute("eviction", "LRU");
        Class evictionClass = typeAliasRegistry.resolveAlias(eviction);
        Long flushInterval = context.getLongAttribute("flushInterval");
        Integer size = context.getIntAttribute("size");
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        boolean blocking = context.getBooleanAttribute("blocking", false);
        Properties props = context.getChildrenAsProperties();
        builderAssistant.useNewCache(typeClass, evictionClass,
                         flushInterval, size, readWrite, blocking, props);
    }
}
从源码可以看到MyBatis读取了那些属性,而且很容易可以到这些属性的默认值。
创建Java的cache对象方法为builderAssistant.useNewCache,我们看看这段代码:
public Cache useNewCache(Class typeClass,
                         Class evictionClass,
                         Long flushInterval,
                         Integer size,
                         boolean readWrite,
                         boolean blocking,
                         Properties props) {
    typeClass = valueOrDefault(typeClass, PerpetualCache.class);
    evictionClass = valueOrDefault(evictionClass, LruCache.class);
    Cache cache = new CacheBuilder(currentNamespace)
            .implementation(typeClass)
            .addDecorator(evictionClass)
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
}
从调用该方法的地方,我们可以看到并没有使用返回值cache,在后面的过程中创建MappedStatement的时候使用了currentCache。
二、使用Cache过程
在系统中,使用Cache的地方在CachingExecutor中:
@Override
public List query(
        MappedStatement ms, Object parameterObject,
        RowBounds rowBounds, ResultHandler resultHandler,
        CacheKey key, BoundSql boundSql) throws SQLException {
  Cache cache = ms.getCache();
获取cache后,先判断是否有二级缓存。
只有通过,或@CacheNamespace,@CacheNamespaceRef标记使用缓存的Mapper.xml或Mapper接口(同一个namespace,不能同时使用)才会有二级缓存。
  if (cache != null) {
如果cache存在,那么会根据sql配置(,
    select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}

    1
    2
    3
像上面这个查询,你会写到那个xml中呢??
不管是写到RoleMapper.xml还是UserRoleMapper.xml,或者是一个独立的XxxMapper.xml中。如果使用了二级缓存,都会导致上面这个查询结果可能不正确。
如果你正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。
这点应该很容易理解。
在我看来,就以MyBatis目前的缓存方式来看是无解的。多表操作根本不能缓存。
如果你让他们都使用同一个namespace(通过)来避免脏数据,那就失去了缓存的意义。
看到这里,实际上就是说,二级缓存不能用。整篇文章介绍这么多也没什么用了。
五、挽救二级缓存?
想更高效率的使用二级缓存是解决不了了。
但是解决多表操作避免脏数据还是有法解决的。解决思路就是通过拦截器判断执行的sql涉及到那些表(可以用jsqlparser解析),然后把相关表的缓存自动清空。但是这种方式对缓存的使用效率是很低的。
设计这样一个插件是相当复杂的,既然我没想着去实现,就不废话了。
最后还是建议,放弃二级缓存,在业务层使用可控制的缓存代替更好。
如何细粒度地控制你的MyBatis二级缓存(mybatis-enhanced-cache插件实现)
时间 2014-12-09 11:31:00 CSDN博客
原文
  http://blog.csdn.net/luanlouis/article/details/41800511
主题 MyBatis 数据库
本文如下组织结构:
    一个关于MyBatis的二级缓存的实际问题
    当前MyBatis二级缓存的工作机制
    mybatis-enhanced-cache插件的设计和工作原理
    mybatis-enhanced-cache 插件的使用实例
1.一个关于MyBatis的二级缓存的实际问题
网友 chanfish 给我抛出的问题
现有 AMapper.xml 中定义了对数据库表 ATable 的CRUD操作,BMapper定义了对数据库表 BTable 的CRUD操作;
假设MyBatis的二级缓存开启,并且 AMapper 中使用了二级缓存, AMapper 对应的二级缓存为 ACache ;
除此之外, AMapper 中还定义了一个跟 BTable 有关的查询语句,类似如下所述:

执行以下操作:
1. 执行 AMapper 中的" selectATableWithJoin " 操作,此时会将查询到的结果放置到 AMapper 对应的二级缓存 ACache 中;
2. 执行 BMapper 中对 BTable 的更新操作( update、delete、insert )后, BTable 的数据更新;
3. 再执行1完全相同的查询,这时候会直接从 AMapper 二级缓存 ACache 中取值,将 ACache 中的值直接返回;
好,问题就出现在第3步上:
由于AMapper的“ selectATableWithJoin ” 对应的SQL语句需要和 BTable 进行join查找,而在第 2 步 BTable 的数据已经更新了,但是第 3 步查询的值是第 1 步的缓存值,已经极有可能跟真实数据库结果不一样,即 ACache 中缓存数据过期了!
总结来看,就是:
对于某些使用了 join连接的查询,如果其关联的表数据发生了更新,join连接的查询由于先前缓存的原因,导致查询结果和真实数据不同步;
从MyBatis的角度来看,这个问题可以这样表述:
对于某些表执行了更新(update、delete、insert)操作后,如何去清空跟这些表有关联的查询语句所造成的缓存;
当前的MyBatis的缓存机制不能很好地处理这一问题,下面我们将从当前的MyBatis的缓存机制入手,分析这一问题:
2. 当前MyBatis二级缓存的工作机制:
当前MyBatis二级缓存的工作机制:
MyBatis二级缓存的一个重要特点:即松散的Cache缓存管理和维护。
一个Mapper中定义的增删改查操作只能影响到自己关联的Cache对象。如上图所示的Mapper namespace1中定义的若干CRUD语句,产生的缓存只会被放置到相应关联的Cache1中,即Mapper namespace2,namespace3,namespace4 中的CRUD的语句不会影响到Cache1。
可以看出,Mapper之间的缓存关系比较松散,相互关联的程度比较弱。
现在再回到上面描述的问题,如果我们将AMapper和BMapper共用一个Cache对象,那么,当BMapper执行更新操作时,可以清空对应Cache中的所有的缓存数据,这样的话,数据不是也可以保持最新吗?
确实这个也是一种解决方案,不过,它会使缓存的使用效率变的很低!AMapper和BMapper的任意的更新操作都会将共用的Cache清空,会频繁地清空Cache,导致Cache实际的命中率和使用率就变得很低了,所以这种策略实际情况下是不可取的。
最理想的解决方案就是:
          对于某些表执行了更新(update、delete、insert)操作后,如何去清空跟这些表有关联的查询语句所造成的缓存;
这样,就是以很细的粒度管理MyBatis内部的缓存,使得缓存的使用率和准确率都能大大地提升。  
基于这个思路,我写了一个对应的mybatis-enhanced-cache 缓存插件,可以很好地支持上述的功能。 
对于上述的例子中,该插件可以实现:当BMapper对BTable执行了更新操作时,指定清除与BTable相关联的selectATableWithJoin查询语句在ACache中产生的缓存。
接下来就来看看这个mybatis-enhanced-cache插件的设计原理吧:
3. mybatis-enhanced-cache插件的设计和工作原理
mybatis-enhanced-cache插件的设计和工作原理
该插件主要由两个构件组成: EnhancedCachingExecutor 和 EnhancedCachingManager 。
EnhancedCachingExecutor 是针对于Executor的拦截器,拦截Executor的几个关键的方法;
EnhancedCachingExecutor 主要做以下几件事:
1. 每当有Executor执行query操作时,
1.1  记录下该查询StatementId和CacheKey,然后将其添加到 EnhancedCachingManager 中;
1.2  记录下该查询StatementId 和此StatementId所属Mapper内的Cache缓存对象引用,添加到 EnhancedCachingManager 中;
2. 每当Executor执行了update操作时,将此 update操作的StatementId传递给 EnhancedCachingManager ,让 EnhancedCachingManager 根据此update的StatementId的配置,去清空指定的查询语句所产生的缓存;
另一个构件: EnhancedCachingManager ,它也是本插件的核心,它维护着以下几样东西:
1. 整个MyBatis的所有查询所产生的CacheKey集合(以statementId分类);
2. 所有的使用过了的查询的statementId 及其对应的Cache缓存对象的引用;
3. update类型的StatementId和查询StatementId集合的映射,用于当Update类型的语句执行时,根据此映射决定应该清空哪些查询语句产生的缓存;
如下图所示:
工作原理:
原理很简单,就是 当执行了某个update操作时,根据配置信息去清空指定的查询语句在Cache中所产生的缓存数据。
如何获取mybatis-enhanced-cache插件源码
1. 源码和jar包2合一压缩包
2. github 地址,直接fork即可:
https://github.com/LuanLouis/mybatis-enhanced-cache
4. mybatis-enhanced-cache 插件的使用实例:
1. 下载 mybatis-enhanced-cache.rar压缩包 ,解压,将其内的mybatis-enhanced-cache-0.0.1-SNAPSHOT.jar添加到项目的classpath下;
2. 配置MyBatis配置文件如下:

      
         
         
      

 

其中, 中的value属性是 StatementId之间的依赖关系的配置文件路径。
3. 配置StatementId之间的依赖关系


  
      
         
      

  


节点配置的是更新语句的statementId,其内的子节点 配置的是当更新语句执行后,应当清空缓存的查询语句的StatementId。子节点可以有多个。
如上的配置,则说明,如果"com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey" 更新语句执行后,由 “com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments” 语句所产生的放置在Cache缓存中的数据都都会被清空。
4. 配置DepartmentsMapper.xml 和EmployeesMapper.xml



  
  
 
   
   
   
   
 

 
 
 
    DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, LOCATION_ID
 

 
 
    update HR.DEPARTMENTS
    set DEPARTMENT_NAME = #{departmentName,jdbcType=VARCHAR},
      MANAGER_ID = #{managerId,jdbcType=DECIMAL},
      LOCATION_ID = #{locationId,jdbcType=DECIMAL}
    where DEPARTMENT_ID = #{departmentId,jdbcType=DECIMAL}
 

   




 
 
   
   
   
   
   
   
   
   
   
   
   
 

 
    EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY,
    COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID
 

 
 
 

5. 测试代码:
package com.louis.mybatis.test;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;
import com.louis.mybatis.model.Department;
import com.louis.mybatis.model.Employee;
/**
* SqlSession 简单查询演示类
* @author louluan
*/
public class SelectDemo3 {
  private static final Logger loger = Logger.getLogger(SelectDemo3.class);
 
  public static void main(String[] args) throws Exception {
    InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(inputStream);
   
    SqlSession sqlSession = factory.openSession(true);
    SqlSession sqlSession2 = factory.openSession(true);
    //3.使用SqlSession查询
    Map params = new HashMap();
    params.put("employeeId",10);
    //a.查询工资低于10000的员工
    Date first = new Date();
    //第一次查询
    List result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
  sqlSession.commit();
  checkCacheStatus(sqlSession);
  params.put("employeeId", 11);
  result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
  sqlSession.commit();
  checkCacheStatus(sqlSession);
  params.put("employeeId", 12);
  result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
  sqlSession.commit();
  checkCacheStatus(sqlSession);
  params.put("employeeId", 13);
  result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
  sqlSession.commit();
  checkCacheStatus(sqlSession);
    Department department = sqlSession.selectOne("com.louis.mybatis.dao.DepartmentsMapper.selectByPrimaryKey",10);
    department.setDepartmentName("updated");
    sqlSession2.update("com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey", department);
    sqlSession.commit();
    checkCacheStatus(sqlSession);
  }
 
 
  public static void checkCacheStatus(SqlSession sqlSession)
  {
    loger.info("------------Cache Status------------");
    Iterator iter = sqlSession.getConfiguration().getCacheNames().iterator();
    while(iter.hasNext())
    {
      String it = iter.next();
      loger.info(it+":"+sqlSession.getConfiguration().getCache(it).getSize());
    }
    loger.info("------------------------------------");
   
  }
}
结果输出:
结果分析:
从上述的结果可以看出,前四次执行了“com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments”语句,EmployeesMapper对应的Cache缓存中存储的结果缓存有1个增加到4个。
当执行了"com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey"后,EmployeeMapper对应的缓存Cache结果被清空了,即"com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey"更新语句引起了EmployeeMapper中的" com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments "缓存的清空。
作者的话
该插件的实现周期比较短,尚未经过性能方面的测试,如果果您对此插件有任何意见或者看法,可以留言一起交流和探讨。
该插件源码已经放到了Github上,可供大家自由修改,github地址:
https://github.com/LuanLouis/mybatis-enhanced-cache

以上就介绍了mybatis缓存的使用及理解,包括了mybatis方面的内容,希望对移动开发有兴趣的朋友有所帮助。

本文网址链接:http://www.codes51.com/article/detail_619003.html

相关图片

相关文章