Spring Boot 缓存的基本用法
一、目的
缓存是用于提升系统的性能,加速系统的访问,降低成本的一种技术。可以将一些高频、热点信息放入缓存中,避免直接从数据库中查询,如商品的页面信息这种经常被访问的数据。
二、JSR-107 缓存规范
为了统一缓存的开发规范、提高系统的扩展性和最小化开发成本等,J2EE 发布了 JSR-107 缓存规范。
Java Caching 定义了 5 个核心接口,分别是CachingProvider, CacheManager, Cache, Entry
和 Expiry。
- CachingProvider定义了创建、配置、获取、管理和控制多个 CacheManager。一个应用可
以在运行期访问多个CachingProvider。 - CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache
存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。 - Cache是一个类似 Map 的数据结构并临时存储以 Key 为索引的值。一个Cache仅被一个CacheManager所拥有。
- Entry是一个存储在Cache中的 key-value 对。
- Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期
的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过 ExpiryPolicy 设置。
三、Spring 缓存抽象
Spring 从 3.1 开始定义了 org.springframework.cache.Cache和 org.springframework.cache.CacheManager接口来统一不同的缓存技术并支持使用 JCache(JSR-107)注解简化我们开发。
几个重要概念&注解:
| Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
|---|---|
| CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
| @Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
| @CacheEvict | 清空缓存 |
| @CachePut | 保证方法被调用,又希望结果被缓存。 |
| @EnableCaching | 开启基于注解的缓存 |
| keyGenerator | 缓存数据时key生成策略 |
| serialize | 缓存数据时value序列化策略 |
四、Demo
1、使用 IDEA 创建 Spring Boot 项目
2、创建相应的数据表
SQL 文件:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`departmentName` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lastName` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`gender` int(2) DEFAULT NULL,
`d_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3、创建 Java Bean 封装数据
package com.yunche.bean;
public class Department {
private Integer id;
private String departmentName;
public Department() {
super();
// TODO Auto-generated constructor stub
}
public Department(Integer id, String departmentName) {
super();
this.id = id;
this.departmentName = departmentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
@Override
public String toString() {
return "Department [id=" + id + ", departmentName=" + departmentName + "]";
}
}
package com.yunche.bean;
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //性别 1 男 0 女
private Integer dId;
public Employee() {
super();
}
public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.dId = dId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getdId() {
return dId;
}
public void setdId(Integer dId) {
this.dId = dId;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
+ dId + "]";
}
}
4、整合 MyBatis
1.配置数据源信息
application.properties:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
#开启驼峰转换规则
mybatis.configuration.map-underscore-to-camel-case=true
2.使用注解版 MyBatis
1)、@MapperScan 指定需要扫描 Mapper 接口所在的包
package com.yunche;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.yunche.mapper")
@SpringBootApplication
public class SpringbootDemoCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoCacheApplication.class, args);
}
}
2)、定义 Mapper 接口中的方法
package com.yunche.mapper;
import com.yunche.bean.Employee;
import org.apache.ibatis.annotations.*;
/**
* @ClassName: EmployeeMapper
* @Description:
* @author: yunche
* @date: 2019/02/01
*/
@Mapper
public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE id = #{id}")
Employee getEmpById(Integer id);
@Update("UPDATE employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
void updateEmp(Employee employee);
@Delete("DELETE FROM employee WHERE id=#{id}")
void deleteEmp(Integer id);
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{d_id})")
void insertEmp(Employee employee);
}
单元测试 Mapper:
package com.yunche;
import com.yunche.bean.Employee;
import com.yunche.mapper.EmployeeMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootDemoCacheApplicationTests {
@Autowired
EmployeeMapper employeeMapper;
@Test
public void contextLoads() {
}
@Test
public void testMapper() {
Employee employee = employeeMapper.getEmpById(1);
System.out.println(employee);
}
}
5、实现 Web 访问
1)、添加 service 包
package com.yunche.service;
import com.yunche.bean.Employee;
import com.yunche.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @ClassName: EmployeeService
* @Description:
* @author: yunche
* @date: 2019/02/01
*/
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
}
2)、添加 controller 包
package com.yunche.controller;
import com.yunche.bean.Employee;
import com.yunche.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: EmployeeController
* @Description:
* @author: yunche
* @date: 2019/02/01
*/
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id) {
Employee employee = employeeService.getEmp(id);
return employee;
}
}
测试:
6、缓存初体验
EmployeeService:
/**
* 将方法的运行结果进行缓存,再次运行该方法时从缓存中返回结果
* CacheManager 管理多个 Cache 组件,Cache 组件进行缓存的 CRUD,每一个缓存组件都有唯一一个名字。
* 属性:
* cacheNames/value:指定缓存组件的名字
* key:缓存数据键值对的 key,默认是方法的参数的值
* keyGenerator:key 的生成器,可以自己指定 key 的生成器的组件 id 与 key 二选一
* cacheManager:指定缓存管理器 cacheResolver:缓存解析器,二者二选一
* condition/unless(否定条件):符合指定条件的情况下才缓存
*
* @param id
* @return
*/
@Cacheable(cacheNames = {"emp"}, condition = "#id % 2 == 1")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
Cache SpEL available metadata:
| 名字 | 位置 | 描述 | 示例 |
|---|---|---|---|
| methodName | root object | 当前被调用的方法名 | #root.methodName |
| method | root object | 当前被调用的方法 | #root.method.name |
| target | root object | 当前被调用的目标对象 | #root.target |
| targetClass | root object | 当前被调用的目标对象类 | #root.targetClass |
| args | root object | 当前被调用的方法的参数列表 | #root.args[0] |
| caches | root object | 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个 cache | #root.caches[0].name |
| argument name | evaluation context | 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0 或#a0 的形式,0 代表参数的索引; | #iban 、 #a0 、 #p0 |
| result | evaluation context | 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式 beforeInvocation=false) | #result |
EmployeeService:
/**
* 更新缓存,既调用方法 (更新数据库),又更新缓存
* 测试步骤:1、查询 1 号员工,将其纳入缓存
* 2、修改 1 号员工
* 3、再次查询 1 号员工,若结果是从缓存中查询数据,且数据为更新后的缓存则测试通过
* @param employee
* @return
*/
@CachePut(cacheNames = {"emp"}, key = "#result.id")
public Employee updateEmp(Employee employee) {
System.out.println("更新" + employee.getdId() + "号员工");
employeeMapper.updateEmp(employee);
return employee;
}
EmployeeController:
@GetMapping("/emp")
public Employee updateEmployee(Employee employee) {
Employee emp = employeeService.updateEmp(employee);
return emp;
}
测试:
EmployeeService:
/**
* 清空缓存
* beforeInvocation:默认为 false 表示在方法调用之后清空缓存,
* 若为 true,则表示在方法调用之前清空缓存
* @param id
*/
@CacheEvict(cacheNames = {"emp"}, beforeInvocation = true/*, key = "#id"*/)
public void deleteEmp(Integer id) {
System.out.println("删除" + id + "号员工");
//employeeMapper.deleteEmp(id); 只测试缓存的删除效果
}
EmployeeController:
@GetMapping("/delemp")
public String deleteEmp(Integer id){
employeeService.deleteEmp(id);
return "success";
}
7、使用 redis 缓存中间件
Spring 的缓存默认使用的是 ConcurrentMapCacheManager 下的 ConcurrentMapCache,是将数据保存在 ConcurrentMap<Object, Object> 中。而在开发中是使用缓存中间件:redis、memcached、ehcache 等。
1.使用 docker 安装 redis(阿里云服务器)
[root@izwz9d74k4cznxtxjeeur9z ~]# docker run -d -p 6379:6379 --name redis_cache docker.io/redis
44f9e905a7db0c0933f3d35ce65dd7041fd985d2da00895713d9765b20781011
2.使用 Redis Desktop Manager 连接阿里云服务器
注意:要在阿里云服务器控制台添加安全规则,确认开放 6379 端口。
3.引入 redis starter 启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
application.properties:
spring.redis.host=x.x.x.x #redis 服务器地址
单元测试 redis 键值是字符串类型 :
/**
* 用于操作 key 和 value 都是字符串的键值
*/
@Autowired
StringRedisTemplate stringRedisTemplate;
/**
* 用于操作 key 和 value 都是对象的键值
*/
@Autowired
RedisTemplate redisTemplate;
/**
* 测试保存字符串
* Redis 常见的五大数据类型
* String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
* stringRedisTemplate.opsForValue()[String(字符串)]
* stringRedisTemplate.opsForList()[List(列表)]
* stringRedisTemplate.opsForSet()[Set(集合)]
* stringRedisTemplate.opsForHash()[Hash(散列)]
* stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
*/
@Test
public void testStringRedis() {
// 追加一个字符串类型的 value
stringRedisTemplate.opsForValue().append("msg", "hello");
//读取一个字符串类型
String value = stringRedisTemplate.opsForValue().get("msg");
System.out.println(value);
}
单元测试 redis 存储对象:
package com.yunche.config;
import com.yunche.bean.Employee;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import java.net.UnknownHostException;
/**
* @ClassName: MyRedisConfig
* @Description:
* @author: yunche
* @date: 2019/02/02
*/
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//改变默认的序列化器
template.setDefaultSerializer(new Jackson2JsonRedisSerializer(Employee.class));
return template;
}
}
@Autowired
RedisTemplate<Object, Employee> empRedisTemplate;
/**
* 测试保存对象
* 1、使对象的类实现 Serializable 接口,表示该类的对象可以序列化
* 2、使用自定义 RedisTemplate 改变默认的序列化机制(jdk)方便观察
*/
@Test
public void testObjectRedis() {
Employee employee = employeeMapper.getEmpById(1);
empRedisTemplate.opsForValue().set("emp-01", employee);
}
4.自定义 RedisCacheManager
此时使用的缓存管理器为 RedisCacheManager,为了使缓存到 Redis 里面的数据达到如上图所示的效果,我们就需要自定义 RedisCacheManager 改变 RedisTemplate 的默认序列化机制(jdk)。
MyRedisConfig:
// Spring Boot 1.x
// @Bean
// public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
// RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
// //key 多了一个前缀
//
// //使用前缀,默认会将 CacheName 作为 key 的前缀
// cacheManager.setUsePrefix(true);
//
// return cacheManager;
// }
/**
* Spring Boot 2.x 以后 RedisCacheManager 构造函数不再接受 RedisTemplate 参数
* @param factory
* @return
*/
@Bean
public RedisCacheManager empRedisCacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer(Employee.class))); //使用 Jackson2JsonRedisSerialize
RedisCacheManager redisCacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
return redisCacheManager;
}
访问 http://localhost:8080/emp/1,Redis 缓存结果如下:
五、参考资料
尚硅谷.Spring Boot 高级篇
Spring Boot 缓存的基本用法的更多相关文章
- Spring boot缓存初体验
spring boot缓存初体验 1.项目搭建 使用MySQL作为数据库,spring boot集成mybatis来操作数据库,所以在使用springboot的cache组件时,需要先搭建一个简单的s ...
- Spring Boot缓存Ehcache
Spring Boot 整合 Ehcache 修改 pom 文件 <!-- Spring Boot 缓存支持启动器 --> <dependency> <groupId ...
- 3步轻松搞定Spring Boot缓存
作者:谭朝红 前言 本次内容主要介绍基于Ehcache 3.0来快速实现Spring Boot应用程序的数据缓存功能.在Spring Boot应用程序中,我们可以通过Spring Caching来快速 ...
- Spring Boot缓存应用实践
缓存是最直接有效提升系统性能的手段之一.个人认为用好用对缓存是优秀程序员的必备基本素质. 本文结合实际开发经验,从简单概念原理和代码入手,一步一步搭建一个简单的二级缓存系统. 一.通用缓存接口 1.缓 ...
- Spring Boot缓存源码分析
前言 项目里面要增加一个应用缓存,原本想着要怎么怎么来整合ehcache和springboot,做好准备配置这个配置那个,结果只需要做三件事: pom依赖 写好一个ehcache的配置文件 在boot ...
- Spring Boot 缓存应用 Ehcache 入门教程
Ehcache 小巧轻便.具备持久化机制,不用担心JVM和服务器重启的数据丢失.经典案例就是著名的Hibernate的默认缓存策略就是用Ehcache,Liferay的缓存也是依赖Ehcache. 本 ...
- Spring Boot 缓存应用 Memcached 入门教程
本章学习 Mmecached 在 Spring Boot 中的使用教程.Memcached 与 Redis 各有好处.本文主要学习 Spring Boot 中如何应用集成 Mmecached spri ...
- spring boot 的一些高级用法
1 spring boot 项目的创建 参考 https://www.cnblogs.com/PerZhu/p/10708809.html 2 首先我们先把Maven里面的配置完成 <depen ...
- Spring Boot缓存注解@Cacheable、@CacheEvict、@CachePut使用
从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...
随机推荐
- 123 c#调用oracle存储过程返回数据集 --通过oracle存储过程返回数据集
c#调用oracle存储过程返回数据集 2008-12-20 10:59:57| 分类: net|字号 订阅 CREATE OR REPLACE PACKAGE pkg_tableTypeIS ...
- bzoj3884
http://www.lydsy.com/JudgeOnline/problem.php?id=3884 拓展欧拉定理 http://blog.csdn.net/Pedro_Lee/article/d ...
- Spark 决策树--分类模型
package Spark_MLlib import org.apache.spark.ml.Pipeline import org.apache.spark.ml.classification.{D ...
- 常用的八大排序算法,含java实例(copy)
说明:转载于http://www.cnblogs.com/qqzy168/archive/2013/08/03/3219201.html 分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序( ...
- E20170606-hm
pipeline n. 管道; 输油管道; 渠道,传递途径; dump vt. 倾倒; 倾销; 丢下,卸下; 摆脱,扔弃; n. 垃圾场; 仓库; 无秩序地累积;
- MySQL基础 — 常用命令
一.连接MySQL 格式: mysql -h主机地址 -u用户名 -p用户密码 1.连接到本机上的MySQ: 首先在打开cmd窗口,输入mysql -uroot -p ,然后空格进入MySQL控制台, ...
- bzoj 1914: [Usaco2010 OPen]Triangle Counting 数三角形【叉积+极角排序+瞎搞】
参考:https://blog.csdn.net/u012288458/article/details/50830498 有点神啊 正难则反,考虑计算不符合要求的三角形.具体方法是枚举每个点,把这个点 ...
- 17年day2
/* 嗯,明天就出发了. 嗯,终于快要结束了. 考试日常挂T1 今天晚上老师们请我们吃水饺,还有一个大蛋糕. 虽然没怎么吃蛋糕23333 还好我的水饺是白菜馅的~~~ 晚上学哥学姐们发视频送祝福,谢谢 ...
- 树网的核 2007年NOIP全国联赛提高组(floyed)
树网的核 2007年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description [问题描述]设 T= ...
- iOS 关于文件操作 NSFileManager
创建文件夹 + (BOOL)creatDir:(NSString *)path { if (path.length==0) { return NO; } NSFileManager *fileMana ...