MyBaits缓存

一级缓存(作用域SqlSession)

  • MyBatis的一级查询缓存(也叫作本地缓存)是基于org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap本地缓存,其作用域是SqlSession
  • 在同一个SqlSession中两次执行相同的 sql 查询语句,第一次执行完毕后,会将查询结果写入到缓存中,第二次会从缓存中直接获取数据,而不再到数据库中进行查询,这样就减少了数据库的访问,从而提高查询效率。
  • 当一个 SqlSession 结束后,该 SqlSession 中的一级查询缓存也就不存在了。
  • myBatis 默认一级查询缓存是开启状态,且不能关闭。
  • 增删改会清空缓存,无论是否commit
  • spring整合mybatis后,非事务环境下,每次操作数据库都使用新的sqlSession对象。因此mybatis的一级缓存无法使用(一级缓存针对同一个sqlsession有效)
  • 在开启事物的情况之下,spring使用threadLocal获取当前资源绑定同一个sqlSession,因此此时一级缓存是有效的

二级缓存(作用域namespace)

  • MyBatis的二级缓存是mapper范围级别的
  • SqlSession关闭后才会将数据写到二级缓存区域
  • 增删改操作,无论是否进行提交commit(),均会清空一级、二级缓存
  • 二级缓存会使用 Least Recently Used (LRU,最近最少使用的)算法来收回。
  • 根据时间表(如 no Flush Interval ,没有刷新间隔),缓存不会以任何时间顺序来刷新 。
  • 缓存会存储集合或对象(无论查询方法返回什么类型的值)的 1024 个引用。
  • 缓存会被视为 read/write (可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改 。
  • 设置方式在mapper文件中添加<cache/>

二级缓存该用么?

多表操作一定不要使用二级缓存,因为多表操作进行更新操作,一定会产生脏数据。而且二级缓存是表级缓存,开销大,没有一级缓存直接使用 HashMap 来存储的效率更高,所以二级缓存并不推荐使用。

二级缓存之Ehcache(本机)

  EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

  优点:

  1. 快速

  2. 简单

  3. 缓存数据有两级:内存和磁盘,因此无需担心容量问题

  4. 缓存数据会在虚拟机重启的过程中写入磁盘

  5. 可以通过RMI、可插入API等方式进行分布式缓存

  6. 具有缓存和缓存管理器的侦听接口

  7. 支持多缓存管理器实例,以及一个实例的多个缓存区域

  8. 提供Hibernate的缓存实现

  9. 多种缓存策略,Ehcache提供了对大数据的内存和硬盘的存储,最近版本允许多实例、保存对象高灵活性、提供LRU、LFU、FIFO淘汰算法,基础属性支持热配置、支持的插件多

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.6</version>
</dependency>
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
     <version>1.1.0</version>
</dependency>
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

  缺点

  1. 使用磁盘Cache的时候非常占用磁盘空间:这是因为DiskCache的算法简单,该算法简单也导致Cache的效率非常高。它只是对元素直接追加存储。因此搜索元素的时候非常的快。如果使用DiskCache的,在很频繁的应用中,很快磁盘会满。

  2. 不能保证数据的安全:当突然kill掉java的时候,可能会产生冲突,EhCache的解决方法是如果文件冲突了,则重建cache。这对于Cache数据需要保存的时候可能不利。当然,Cache只是简单的加速,而不能保证数据的安全。如果想保证数据的存储安全,可以使用Bekeley DB Java Edition版本。这是个嵌入式数据库。可以确保存储安全和空间的利用率。

二级缓存之Redis(分布式适用)

pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> 
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.7.0</version>
</dependency>

redishelper

package com.zqh.cache;

import com.zqh.config.ApplicationContextHolder;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 1.使用@Component的方式,是spring的方式,会生成一个RedisCache的实例,纳入spring容器中,
 *    在这个容器中实例,是可以注入RedisTemplate。
 * 2.因为在每个mapper中,配置了二级缓存,对应的mapper会重新生成实例,生成的这个实例没有按照spring的规则来生成。
 *   所以这个类中加入了 @Resource @AutoWire都是无法获取。
 * 3.那么如何获取spring容器中的对象了?就需要先拿到spring容器,然后从容器中去手动取。
 */

public class RedisCache implements Cache {  //要想使用Mybatis的二级缓存,必须要实现Cache接口

    private RedisTemplate<Object, Object> redisTemplate;

    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    private String id;

    // id的作用是将不同的mapper作一个区分
    public RedisCache(String id) {
        this.id = id;
    }

    // 获取RedisTemplate
    private RedisTemplate<Object, Object> getRedisTemplate() {
        this.redisTemplate = ApplicationContextHolder.getRedisTemplate();
        return redisTemplate;
    }

    @Override
    public void putObject(Object key, Object value) {
        getRedisTemplate().opsForValue().set(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return getRedisTemplate().opsForValue().get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return getRedisTemplate().delete(key);
    }

    @Override
    public int getSize() {
        return 1;
    }

    @Override
    public String getId() {
        return this.id;
    }

    // 不用实现
    @Override
    public void clear() {
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.reentrantReadWriteLock;
    }
}
View Code

获取ApplicationContext

package com.zqh.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

/**
 * spring提供了很多的接口 XXXXAware, 这一类的接口比较的特殊, 那么spring容器在启动的时候
 * 如果检测某个类实现了这一类接口,那么会去调用实现了该接口方法的实现。
 */
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    // 该放方法会自动将spring容器的类 ApplicationContext, 传入给该方法。
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHolder.applicationContext = applicationContext;
    }

    // 从容器中获取RedisTemplate
    public static RedisTemplate getRedisTemplate() {
        return applicationContext.getBean("redisTemplate", RedisTemplate.class);
    }
}
View Code

mapper配置

    <!--
       在这里配置缓存,那么每个Mapper都会去生成 RedisCache的实现类
     -->
     <!--
        flushInterval: 清空缓存的时间间隔,单位为毫秒; 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用更新语句时刷新。
        size: 可以被设置为任意正整数, 缓存的数量,默认是1024;
        evication: LRU 移除最长时间不被使用的对象。
        blocking: 默认是false;
     -->
    <cache size="1024" type="com.qf.cache.RedisCache"></cache>
View Code

Mybatis的二级缓存作用相对有限,特别是在分布式场景及复杂业务场景。建议不开启,直接用在逻辑层自己控制redis。

原文地址:https://www.cnblogs.com/wishonline/p/13395830.html