Spring Boot缓存

概述

  Spring提供了对应用程序添加缓存的支持。从本质上讲,将缓存应用于方法上,从而根据缓存中的信息减少执行次数。当开发者调用一个方法时,将方法的参数和返回值作为Key/Value缓存起来,当再次调用这个方法时,如果缓存中存在对应数据,就从缓存中获取数据,否则再去执行该方法。

支持

Spring并没有提供缓存的实现,而是提供了一套Api,可以自由选择缓存的实现。目前Spring Boot支持的缓存有如下几种(Spring Boot会按顺序检测以下提供的程序):

  • Generic
  • JCache(JSR-107)(EhCache 3, Hazelcast, Infinispan, and others)
  • EhCache 2.x
  • Hazelcast
  • Infinispan
  • Couchbase
  • Readis
  • Caffeine
  • Simple

可以通过设置属性来指定提供缓存的程序。

无论使用哪种缓存实现不同的只是缓存配置,使用的缓存注解是一致的

注解 介绍
@EnableCaching 启用S​​pring的注释驱动的缓存管理功能
@CacheConfig 当此批注出现在给定的类上时,它为该类中定义的任何缓存操作提供一组默认设置。
@Cacheable 表示可以缓存调用方法(或类中的所有方法)结果的注释。每次调用指定方法时,将进行缓存行为,检查是否已缓存该方法参数和结果。如果在缓存中找不到键的值,则将调用目标方法并将返回的值存储在关联的缓存中。
@CacheEvict 表示方法(或类上的所有方法)触发缓存逐出操作的注解。
@CachePut 表示方法(或类上的所有方法)触发缓存放置操作的注释。
@Caching 此批注可用作元批注,以创建具自定义组合批注。

一、Ehcache 2.x 缓存

在项目的pom.xml文件中添加以下依赖:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
添加缓存配置文件

如果存在Ehcache的依赖,并且在classpath下有名为ehcache.xml的文件,那么EhCacheCacheManager将会自动作为缓存的提供者。因此,在resources目录下创建ehcache.xml文件作为Ehcach缓存的配置文件。

如果未配置ehcache.xml,则Ehcache依赖包下的ehcache-failsafe.xml是ehcache的默认配置。

<ehcache>
    <!--diskStore元素是可选的。-->
    <!--如果为任何缓存启用了overflowToDisk或diskPersistent,则必须对其进行配置。-->
    <!--diskStore只有一个属性 - path。这是将在其中创建.data和.index文件的目录路径。-->
    <!--如果路径是Java系统属性,则将其替换为正在运行的VM中的值-->
    <!--user.home - 用户的主目录-->
    <!--user.dir - 用户的当前工作目录-->
    <!--java.io.tmpdir - 默认临时文件路径-->
    <!--ehcache.disk.store.dir - 在命令行上指定的系统属性(例:java -Dehcache.disk.store.dir=/u01/myapp/diskdir)-->
    <!--子目录可以在属性后指定 例:java.io.tmpdir/cache-->
    <diskStore path="java.io.tmpdir/cache"/>

    <!--强制性默认缓存配置-->
    <!--这些设置将应用于使用CacheManager.add(String cacheName)创建的缓存。-->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <!--localTempSwap - 该策略允许缓存在缓存操作期间使用本地磁盘。磁盘存储是临时的,并在重新启动后清除。-->
        <!--当策略为"none"时,所有缓存都保留在内存中(磁盘无溢出,磁盘无持久性)。-->
        <persistence strategy="localTempSwap"/>
    </defaultCache>

    <cache name="student"
           maxElementsInMemory="10000"
           eternal="false"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"/>
</ehcache>
缓存配置 <cache/>:
属性 介绍
name 设置缓存的名称,用于标识缓存。
maxElementsInMemory 设置在内存中创建的最大对象数(0 ==无限制)。
maxElementsOnDisk 设置将在DiskStore中维护的最大对象数。默认值为零,表示无限制。
eternal 设置元素是否一直存在。如果为true,超时将被忽略。
overflowToDisk 设置当内存中的缓存达到maxInMemory限制时元素是否可以溢出到磁盘。
timeToIdleSeconds 设置元素过期之前的空闲时间,即缓存创建以后最后一次访问缓存的时间到超时失效时的时间间隔。值为0表示无穷大,默认值为0。
timeToLiveSeconds 设置元素过期之前的生存时间,即从创建时间到元素过期之间的间隔。值为0表示一直存在,默认值为0。
diskPersistent 磁盘存储在虚拟机重新启动后是否仍然存在,默认值为false。
diskExpiryThreadIntervalSeconds 做元素失效监测以及清除工作的线程运行间隔时间,默认值为120秒。
diskSpoolBufferSizeMB 磁盘缓冲区大小,默认30MB(内部以字节为单位,设置的值转换为字节后不可超过正整数表示范围)。
memoryStoreEvictionPolicy 达到maxElementsInMemory限制后,将强制执行清除策略。默认策略是最近最少使用(LRU),其他可用策略先进先出(指定为FIFO)和不常用(指定为LFU)。
开启缓存

在项目的入口类上添加@EnableCaching注解开启缓存。

@EnableCaching
@SpringBootApplication
public class EhcacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(EhcacheApplication.class, args);
    }

}
创建数据接口
@Service
@CacheConfig(cacheNames = "student")
public class StudentService {

    @Resource
    private StudentMapper studentMapper;

    @Cacheable
    public Student findById(Integer id) {
        Student student = studentMapper.findById(id);
        System.out.println("查询 :" + JSON.toJSONString(student));
        return student;
    }

    @CachePut(key = "#student.id")
    public Student insert(Student student) throws Exception {
        int status = studentMapper.insert(student);
        if (status == 0) {
            throw new Exception("Insert failed");
        }
        System.out.println("插入 :" + JSON.toJSONString(student));
        return student;
    }

    @CacheEvict(key = "#student.id")
    public Student delete(Student student) throws Exception {
        int status = studentMapper.delete(student);
        if (status == 0) {
            throw new Exception("Delete failed");
        }
        System.out.println("删除 :" + JSON.toJSONString(student));
        return student;
    }

    @CachePut(key = "#student.id")
    public Student update(Student student) throws Exception {
        int status = studentMapper.update(student);
        if (status == 0) {
            throw new Exception("Update failed");
        }
        System.out.println("更新 :" + JSON.toJSONString(student));
        return student;
    }

}
创建请求接口
@RestController
@RequestMapping("/std")
public class StudentController {

    @Resource
    private StudentService studentService;

    /**
     * 查询
     */
    @RequestMapping("/sel")
    public String findById(Student student) {
        Map<String, Object> msg = new HashMap<>();
        try {
            student = studentService.findById(student.getId());
            msg.put("status", 200);
            msg.put("result", student);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return JSON.toJSONString(msg);
    }

    /**
     * 插入
     */
    @RequestMapping("/ins")
    public String insert(Student student) {
        Map<String, Object> msg = new HashMap<>();
        try {
            studentService.insert(student);
            msg.put("status", 200);
            msg.put("message", "插入成功");
        } catch (Exception e) {
            msg.put("status", 400);
            msg.put("message", "插入失败");
        }
        return JSON.toJSONString(msg);
    }


    /**
     * 删除
     */
    @RequestMapping("/del")
    public String delete(Student student) {
        Map<String, Object> msg = new HashMap<>();
        try {
            studentService.delete(student);
            msg.put("status", 200);
            msg.put("message", "删除成功");
        } catch (Exception e) {
            msg.put("status", 400);
            msg.put("message", "删除失败");
        }
        return JSON.toJSONString(msg);
    }

    /**
     * 更新
     */
    @RequestMapping("/upd")
    public String update(Student student) {
        Map<String, Object> msg = new HashMap<>();
        try {
            studentService.update(student);
            msg.put("status", 200);
            msg.put("message", "更新成功");
        } catch (Exception e) {
            msg.put("status", 400);
            msg.put("message", "更新失败");
        }
        return JSON.toJSONString(msg);
    }

}
测试

向数据库中添加数据:

(一) 查询

连续访问查询接口页面并观察输出信息:

查询
控制台

根据输出信息发现,在浏览器中多次获取数据,数据查询方法只执行了一遍。

(二) 更新

访问更新接口:

更新

再次访问查询接口:

查询

观察控制台:


控制台

发现访问更新接口后再次访问查询接口,查询接口并没有再次从数据库获取数据,而是从缓存中获取,所以更新接口返回的结果会覆盖指定参数键的缓存。

(三) 插入

访问插入接口:

插入

访问查询接口:

查询

观察控制台:

控制台

发现插入数据后再次查询并没有从数据库中获取数据,而是从缓存中获取。因为插入时对参数和返回的结果进行了缓存。

(四) 删除

访问删除接口:

删除

访问查询接口:

查询

观察控制台:

控制台

发现访问删除接口后数据库中的数据被删除,再次访问查询接口进行查询发现触发了查询方法,说明删除数据时缓存同时也被删除了。

二、Redis 单机缓存

和Ehcache一样,如果在classpath下存在Redis并且Redis已经配置好,此时会默认使用RedisCacheManager作为缓存的提供者。

在项目的pom.xml文件中添加以下依赖(与之前的依赖相比不同的只是缓存的提供者):

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>

添加项目配置:

spring:
  datasource:
    url: jdbc:mysql://xxx.xxx.xxx.xxx/user
    username: xxxx
    password: xxxxxx

  # 缓存配置
  cache:
    # 配置缓存名,多个缓存可使用逗号分隔(one,two)
    cache-names: student
    # 缓存存在时间
    redis:
      time-to-live: 120s
  redis:
    host: xxx.xxx.xxx.xxx
    port: xxxx

之后的流程与之前一致就不在阐述了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271