如题,今天介绍 SpringBoot 的数据缓存。做过开发的都知道程序的瓶颈在于数据库,我们也知道内存的速度是大大快于硬盘的,当需要重复获取相同数据时,一次又一次的请求数据库或者远程服务,导致大量时间耗费在数据库查询或远程方法调用上,导致性能的恶化,这便是数据缓存要解决的问题。

Spring 的缓存支持

Spring 定义了 org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口用于统一不同的缓存技术。其中,CacheManager 是 Spring 提供的各种缓存技术的抽象接口,Cache 接口则是包含了缓存的各种操作(增加,删除,获取缓存,一般不会直接和此接口打交道)。

1、Spring 支持的 CacheManager

针对不同的缓存技术,实现了不同的 CacheManager ,Spring 定义了下表所示的 CacheManager:

CacheManager 描述
SimpleCacheManager 使用简单的 Collection 来存储缓存,主要用于测试
ConcurrentMapCacheManager 使用 ConcurrentMap 来存储缓存
NoOpCacheManager 仅测试用途,不会实际缓存数据
EhCacheCacheManager 使用 EhCache 作为缓存技术
GuavaCacheManager 使用 Google Guava 的 GuavaCache 作为缓存技术
HazelcastCacheManager 使用 Hazelcast 作为缓存技术
JCacheCacheManager 支持 JCache(JSR-107) 标准的实现作为缓存技术,如 ApacheCommonsJCS
RedisCacheManager 使用 Redis 作为缓存技术

在使用以上任意一个实现的 CacheManager 的时候,需注册实现的 CacheManager 的 Bean,如:

@Bean
public EhCacheCacheManager cacheManager(CacheManager
ehCacheCacheManager){
return new EhCacheCacheManager(ehCacheCacheManager);
}

注意,每种缓存技术都有很多的额外配置,但配置 cacheManager 是必不可少的。

2、声明式缓存注解

Spring 提供了 4 个注解来声明缓存规则(又是使用注解式的 AOP 的一个例子)。4 个注解如下表示:

注解 解释
@Cacheable 在方法执行前 Spring 先查看缓存中是否有数据,若有,则直接返回缓存数据;若无数据,调用方法将方法返回值放入缓存中
@CachePut 无论怎样,都会将方法的返回值放到缓存中。
@CacheEvict 将一条或多条数据从缓存中删除
@Caching 可以通过 @Caching 注解组合多个注解策略在一个方法上

@Cacheable、@CachePut、@CacheEvict 都有 value 属性,指定的是要使用的缓存名称;key 属性指定的是数据在缓存中存储的键。

3、开启声明式缓存支持

开启声明式缓存很简单,只需在配置类上使用 @EnableCaching 注解即可,例如:

@Configuration
@EnableCaching
public class AppConfig{ }

SpringBoot 的支持

在 Spring 中使用缓存技术的关键是配置 CacheManager ,而 SpringBoot 为我们配置了多个 CacheManager 的实现。

它的自动配置放在 org.springframework.boot.autoconfigure.cache 包中。

在不做任何配置的情况下,默认使用的是 SimpleCacheConfiguration ,即使用 ConcurrentMapCacheManager。SpringBoot 支持以前缀来配置缓存。例如:

spring.cache.type= # 可选 generic、ehcache、hazelcast、infinispan、jcache、redis、guava、simple、none
spring.cache.cache-names= # 程序启动时创建的缓存名称
spring.cache.ehcache.config= # ehcache 配置文件的地址
spring.cache.hazelcast.config= # hazelcast配置文件的地址
spring.cache.infinispan.config= # infinispan配置文件的地址
spring.cache.jcache.config= # jcache配置文件的地址
spring.cache.jcache.provider= # 当多个 jcache 实现在类路径的时候,指定 jcache 实现
# 等等。。。

在 SpringBoot 环境下,使用缓存技术只需要在项目中导入相关缓存技术的依赖包,并在配置类中使用 @EnableCaching 开启缓存支持即可。

代码实现

本文将以 SpringBoot 默认的 ConcurrentMapCacheManager 作为缓存技术,演示 @Cacheable、@CachePut、@CacheEvict。

1、准备工作

  • IDEA
  • JDK 1.8
  • SpringBoot 2.1.3

2、Pom.xml 文件依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.nasus</groupId>
<artifactId>cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cache</name>
<description>cache Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<!-- cache 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- JPA 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- web 启动类 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql 数据库连接类 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- lombok 依赖,简化实体 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 单元测试类 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

注释很清楚,无需多言。不会就谷歌一下。

3、Application.yaml 文件配置

spring:
# 数据库相关
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true
username: root
password: 123456
# jpa 相关
jpa:
hibernate:
ddl-auto: update # ddl-auto: 设为 create 表示每次都重新建表
show-sql: true

4、实体类

package com.nasus.cache.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; @Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Student { @Id
@GeneratedValue
private Integer id; private String name; private Integer age; }

5、dao 层

package com.nasus.cache.repository;

import com.nasus.cache.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; @Repository
public interface StudentRepository extends JpaRepository<Student,Integer> {
}

6、service 层

package com.nasus.cache.service;

import com.nasus.cache.entity.Student;

public interface StudentService {

    public Student saveStudent(Student student);

    public void deleteStudentById(Integer id);

    public Student findStudentById(Integer id);

}

实现类:

package com.nasus.cache.service.impl;

import com.nasus.cache.entity.Student;
import com.nasus.cache.repository.StudentRepository;
import com.nasus.cache.service.StudentService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; @Service
public class StudentServiceImpl implements StudentService { // 使用 slf4j 作为日志框架
private static final Logger LOGGER = LoggerFactory.getLogger(StudentServiceImpl.class); @Autowired
private StudentRepository studentRepository; @Override
@CachePut(value = "student",key = "#student.id")
// @CachePut 缓存新增的或更新的数据到缓存,其中缓存名称为 student 数据的 key 是 student 的 id
public Student saveStudent(Student student) {
Student s = studentRepository.save(student);
LOGGER.info("为id、key 为{}的数据做了缓存", s.getId());
return s;
} @Override
@CacheEvict(value = "student")
// @CacheEvict 从缓存 student 中删除 key 为 id 的数据
public void deleteStudentById(Integer id) {
LOGGER.info("删除了id、key 为{}的数据缓存", id);
//studentRepository.deleteById(id);
} @Override
@Cacheable(value = "student",key = "#id")
// @Cacheable 缓存 key 为 id 的数据到缓存 student 中
public Student findStudentById(Integer id) {
Student s = studentRepository.findById(id).get();
LOGGER.info("为id、key 为{}的数据做了缓存", id);
return s;
}
}

代码讲解看注释,很详细。

7、controller 层

package com.nasus.cache.controller;

import com.nasus.cache.entity.Student;
import com.nasus.cache.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/student")
public class StudentController { @Autowired
private StudentService studentService; @PostMapping("/put")
public Student saveStudent(@RequestBody Student student){
return studentService.saveStudent(student);
} @DeleteMapping("/evit/{id}")
public void deleteStudentById(@PathVariable("id") Integer id){
studentService.deleteStudentById(id);
} @GetMapping("/able/{id}")
public Student findStudentById(@PathVariable("id") Integer id){
return studentService.findStudentById(id);
} }

8、application 开启缓存功能

package com.nasus.cache;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching; @EnableCaching // 开启缓存功能
@SpringBootApplication
public class CacheApplication { public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}

测试

测试前,先看一眼数据库当前的数据,如下:

1、测试 @Cacheable

访问 http://localhost:8080/student/able/2 控制台打印出了 SQL 查询语句,以及指定日志。说明这一次程序是直接查询数据库得到的结果。

2019-02-21 22:54:54.651  INFO 1564 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 11 ms
Hibernate: select student0_.id as id1_0_0_, student0_.age as age2_0_0_, student0_.name as name3_0_0_ from student student0_ where student0_.id=?
2019-02-21 22:54:59.725 INFO 1564 --- [nio-8080-exec-1] c.n.c.service.impl.StudentServiceImpl : 为id、key 为2的数据做了缓存

postman 第一次测试结果 :

再次访问 http://localhost:8080/student/able/2 结果如下图。但控制台无 SQL 语句打印,也无为id、key 为2的数据做了缓存这句话输出。

说明 @Cacheable 确实做了数据缓存,第二次的测试结果是从数据缓存中获取的,并没有直接查数据库。

2、测试 @CachePut

如下图,postman 访问 http://localhost:8080/student/put 插入数据:

下面是控制台打印出了 SQL Insert 插入语句,以及指定日志。说明程序做了缓存。

Hibernate: insert into student (age, name, id) values (?, ?, ?)
2019-02-21 23:12:03.688 INFO 1564 --- [nio-8080-exec-8] c.n.c.service.impl.StudentServiceImpl : 为id、key 为4的数据做了缓存

插入数据返回的结果:

数据库中的结果:

访问 http://localhost:8080/student/able/4 Postman 结果如下图。控制台无输出,验证了 @CachePut 确实做了缓存,下图数据是从缓存中获取的。

3、测试 @CacheEvict

postman 访问 http://localhost:8080/student/able/3 为 id = 3 的数据做缓存。

postman 再次访问 http://localhost:8080/student/able/3 确认数据是从缓存中获取的。

postman 访问 http://localhost:8080/student/evit/3

从缓存中删除 key 为 3 的缓存数据:

Hibernate: select student0_.id as id1_0_0_, student0_.age as age2_0_0_, student0_.name as name3_0_0_ from student student0_ where student0_.id=?
2019-02-21 23:26:08.516 INFO 8612 --- [nio-8080-exec-2] c.n.c.service.impl.StudentServiceImpl : 为id、key 为3的数据做了缓存
2019-02-21 23:27:01.508 INFO 8612 --- [nio-8080-exec-4] c.n.c.service.impl.StudentServiceImpl : 删除了id、key 为3的数据缓存

再次 postman 访问 http://localhost:8080/student/able/3 观察后台,重新做了数据缓存:

Hibernate: select student0_.id as id1_0_0_, student0_.age as age2_0_0_, student0_.name as name3_0_0_ from student student0_ where student0_.id=?
2019-02-21 23:27:12.320 INFO 8612 --- [nio-8080-exec-5] c.n.c.service.impl.StudentServiceImpl : 为id、key 为3的数据做了缓存

这一套测试流程下来,证明了 @CacheEvict 确实删除了数据缓存。

源码下载

https://github.com/turoDog/Demo/tree/master/springboot_cache_demo

切换缓存技术

切换缓存技术除了在 pom 文件加入相关依赖包配置以外,使用方式与上面的代码演示一样。

1、切换 EhCache

在 pom 中添加 Encache 依赖:

<!-- EhCache 依赖 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

Ehcache 所需配置文件 ehcache.xml 只需放在类路径(resource 目录)下,SpringBoot 会自动扫描,如:

<?xml version="1.0" encoding="UTF-8">
<ehcache>
<cache name="student" maxElementsInMmory="1000">
<ehcache>

SpringBoot 会自动配置 EhcacheManager 的 Bean。

2、切换 Guava

只需在 pom 中加入 Guava 依赖即可:

<!-- GuavaCache 依赖 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>

SpringBoot 会自动配置 GuavaCacheManager 的 Bean。

3、切换 RedisCache

与 Guava 一样,只需在 pom 加入依赖即可:

<!-- cache 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>

SpringBoot 会自动配置 RedisCacheManager 以及 RedisTemplate 的 Bean。

此外,切换其他缓存技术的方式也是类似。这里不做赘述。

最后

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「一个优秀的废人」,关注后回复「1024」送你一套完整的 java 教程。



Spring Boot2 系列教程 (十一) | 整合数据缓存 Cache的更多相关文章

  1. Spring Boot2 系列教程 (十七) | 整合 WebSocket 实现聊天室

    微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 昨天那篇介绍了 WebSocket 实现广播,也即服务器端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器.但 ...

  2. Spring Boot2 系列教程 (十三) | 整合 MyBatis (XML 版)

    前言 如题,今天介绍 SpringBoot 与 Mybatis 的整合以及 Mybatis 的使用,之前介绍过了 SpringBoot 整合MyBatis 注解版的使用,上一篇介绍过 MyBatis ...

  3. Spring Boot2 系列教程(十一)Spring Boot 中的静态资源配置

    当我们使用 SpringMVC 框架时,静态资源会被拦截,需要添加额外配置,之前老有小伙伴在微信上问松哥 Spring Boot 中的静态资源加载问题:"松哥,我的 HTML 页面好像没有样 ...

  4. Spring Boot2 系列教程(三十)Spring Boot 整合 Ehcache

    用惯了 Redis ,很多人已经忘记了还有另一个缓存方案 Ehcache ,是的,在 Redis 一统江湖的时代,Ehcache 渐渐有点没落了,不过,我们还是有必要了解下 Ehcache ,在有的场 ...

  5. Spring Boot2 系列教程(二十)Spring Boot 整合JdbcTemplate 多数据源

    多数据源配置也算是一个常见的开发需求,Spring 和 SpringBoot 中,对此都有相应的解决方案,不过一般来说,如果有多数据源的需求,我还是建议首选分布式数据库中间件 MyCat 去解决相关问 ...

  6. CRL快速开发框架系列教程十一(大数据分库分表解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  7. Spring Boot2 系列教程(二十一)整合 MyBatis

    前面两篇文章和读者聊了 Spring Boot 中最简单的数据持久化方案 JdbcTemplate,JdbcTemplate 虽然简单,但是用的并不多,因为它没有 MyBatis 方便,在 Sprin ...

  8. Spring Boot2 系列教程(十)Spring Boot 整合 Freemarker

    今天来聊聊 Spring Boot 整合 Freemarker. Freemarker 简介 这是一个相当老牌的开源的免费的模版引擎.通过 Freemarker 模版,我们可以将数据渲染成 HTML ...

  9. Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis

    在 Redis 出现之前,我们的缓存框架各种各样,有了 Redis ,缓存方案基本上都统一了,关于 Redis,松哥之前有一个系列教程,尚不了解 Redis 的小伙伴可以参考这个教程: Redis 教 ...

随机推荐

  1. java UDP传输

    ①:只要是网络传输,必须有socket . ②:数据一定要封装到数据包中,数据包中包括目的地址.端口.数据等信息. 直接操作udp不可能,对于java语言应该将udp封装成对象,易于我们的使用,这个对 ...

  2. [学习笔记]k短路

    A*:我已经忘了怎么写了,反正n=30,m=1000都能卡掉... 正解:可持久化左偏树+堆维护可能集合 原论文:http://www.docin.com/p-1387370338.html 概括: ...

  3. win10 uwp 使用 LayoutTransformer

    如果需要使用旋转,那么很容易把图片旋转的布局被裁剪.如果需要旋转的控件还在指定的 Grid 内,就需要使用布局的旋转.本文告诉大家如何使用 LayoutTransformer.需要知道,uwp是没有 ...

  4. 使用iOSSelect.js实现iOS的select下拉选择日期的联动效果

    引入文件: <link rel="stylesheet" href="/static/css/iosSelect.css"> <script ...

  5. Linux 内核释放函数和 kobject 类型

    讨论中仍然缺失的一个重要事情是当一个 kobject 的引用计数到 0 时会发生什么. 创建 kobject 的代码通常不知道什么时候要发生这个情况; 如果它知道, 在第一位使用一个引 用计数就没有意 ...

  6. Linux 内核 标准 PCI 配置寄存器

    一些 PCI 配置寄存器是要求的, 一些是可选的. 每个 PCI 设备必须包含有意 义的值在被要求的寄存器中, 而可选寄存器的内容依赖外设的实际功能. 可选的字段不被 使用, 除非被要求的字段的内容指 ...

  7. 记一次奇葩事——html5可能不支持window.onscroll函数

    只在html5里遇到,html4没事:拿出来聊聊,路过帮忙解答下!!! 不正常的 <!doctype html><html><head><meta chars ...

  8. RocketMQ各组件介绍

    Rocket 架构主要分为4部分: Producer 消息发布者,支持分布式集群部署.Produer 通过 MQ 负载均衡模块选择相应 Broker 中的 queue 进行消息投递,投递过程支持快速失 ...

  9. 【Linux】grep笔记

    Linux grep命令用于查找文件里符合条件的字符串. 参数: -a 或 --text : 不要忽略二进制的数据. -A<显示行数> 或 --after-context=<显示行数 ...

  10. hibernate配置文件模板

    hibernate.cfg.xml 配置文件模版: <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-config ...