一 JSR107
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接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
- 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
三、几个重要概念&缓存注解
1.开启基于注解的缓存
@EnableCaching 开启基于注解的缓存
2.标注缓存注解
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。
@Cacheing 可以同时写多个类型的缓存
@CacheConfig 在类上统一配置一些缓存属性比如ChacheNames
- 案例
package com.lc.springboot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* 一、搭建基本环境
* 1.导入数据库文件 创建department和employee表
* 2.创建Javabean
* 3.整合Mybatis操作数据库
* 1)配置数据源信息
* 2)使用注解板的mybatis
* 使用@Mapperscan指定需要扫描的Mapper包
* 二、快速体验缓存
* 步骤:
* 1.开启基于注解的缓存
* @EnableCaching 开启基于注解的缓存
* 2.标注缓存注解
* @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
* @CacheEvict 清空缓存
* @CachePut 报证方法被调用,又希望结果被缓存。
*
*
*/
@MapperScan("com.lc.springboot.mapper")
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
package com.lc.springboot.service;
import com.lc.springboot.bean.Employee;
import com.lc.springboot.mapper.EmployeeMapper;
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.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import java.sql.SQLOutput;
@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
/**
* 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取;不在调用方法
* 原理:
* 1.自动配置:CacheAutoConfiguration
* 2.缓存的配置类
* org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
* org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration JSR107的配置
* org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
* org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
* org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
* org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
* org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
* 3.默认生效的的缓存配置 SimpleCacheConfiguration
* 4.给容器注册了一个 CacheManager:ConcurrentMapCache
* 5.可以获取和创建ConcurrentMapCache类型的缓存组件,将数据保存在ConcurrentMap中
* 运行流程:
* 1.方法运行之前先去查询cache(缓存组件),按照cacheNames指定的名字获取(cacheManager 先获取相应的缓存)
* 如果没有cache组件会自动创建
* 2.会去cache中根据key查找缓存内容;key默认是方法的参数 ;
* key是按照某种策略生成的, 默认使用SimpleKeyGenerator生成
* 如果没有参数:key=new SimpleKey()
* 如果有一个参数: key = 参数的值
* 如果有多个参数:key=new SimpleKey(param)
* 3.没有查到缓存就会调用目标方法
* 4.将目标方法返回的结果放进缓存
* 先检查是否存在,不存在则执行方法,默认使用方法的参数值作为key去查询缓存
* 属性介绍:
* cacheNames/values:指定缓存的名字
* key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 :id-方法的返回值
* 编写SpEl #id:参数id的值 #a0 #p0 #root.args[0]
* key="#root.methodName+'['+#id+']'"
* keyGenerator:key的生成器,可以自己指定key的生成器组件id
* key/keyGenerator 二选一使用
* cacheManager:指定缓存管理器或者是指定cacheResolver解析器
* condition:指定符合条件的情况下才缓存 condition = "#id>0"
* 互逆
* unless: 用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。
* 条件为true不会缓存,false才缓存
* unless = "#result==null"
* sync: 是否使用异步模式
* @param id
* @return
*/
@Cacheable(cacheNames = {"emp"}/*, keyGenerator = "myKeyGenerator"*/)
public Employee getEmp(Integer id){
System.out.println("查询" + id + "号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
/**
* @CachePut : 即调用方法有更新缓存
*
* 运行时机:
* 1.先调用方法
* 2.将目标方法的加过缓存起来
* 更新要注意cacheNames和key这样查询时也显示正常的数据
* 同步更新缓存
*
*
* @param employee
* @return
*/
@CachePut(cacheNames = "emp",key="#result.id")
public Employee updateEmp(Employee employee){
System.out.println("员工更新"+ employee);
employeeMapper.updateEmp(employee);
return employee;
}
/**
* @CacheEvict 清除缓存
* key :指定要清除的数据
* allEntries: 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
* beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,
* 缺省情况下,如果方法执行抛出异常,则不会清空缓存
*/
@CacheEvict(cacheNames = "emp", key="#id")
public void deleteEmp(Integer id){
System.out.println("删除" + 1);
//employeeMapper.deleteEmp(id);
}
@Caching(
cacheable = {
@Cacheable(value = "emp", key="#lastName")
},
put = {
@CachePut(value = "emp", key="#result.id"),
@CachePut(value = "emp", key="#result.email")
}
)
public Employee getEmpByLastName(String lastName){
Employee employee = employeeMapper.getEmpByLastName(lastName);
return employee;
}
}
==== 可以使用
@CacheConfig(cacheNames = “emp”)
来给类中的所有缓存指定name
@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {
// 自定义key生成策略
package com.lc.springboot.config;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object o, Method method, Object... objects) {
return method.getName()+ "[" + Arrays.asList(objects).toString() + "]";
}
};
}
}
四、缓存使用
1、引入spring-boot-starter-cache模块
2、@EnableCaching开启缓存
3、使用缓存注解
4、切换为其他缓存
(一)整合Redis缓存(spring boot 2.0)
在springboot1.x系列中,其中使用的是jedis,但是到了springboot2.x其中使用的是Lettuce。 此处springboot2.x,所以使用的是Lettuce。
关于jedis跟lettuce的区别:
- Lettuce 和 Jedis 的定位都是Redis的client,所以他们当然可以直接连接redis server。
- Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
- Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
1、安装Redis
docker run -p 6379:6379
-v /zzyyuse/myredis/data:/data
-v /zzyyuse/myredis/conf/redis.conf:/usr/local/etc/redis/redis.conf
-d redis:3.2 redis-server /usr/local/etc/redis/redis.conf --appendonly yes
2. maven 依赖导入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
3.redis参数配置
# 配置Redis
# 缓存时长,单位秒
cache.default-exp=72
spring.redis.database=0
spring.redis.host=192.168.43.230
spring.redis.port=6379
# 密码
spring.redis.password=
# 连接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000
# 连接池中的最大空闲连接,默认值也是8
spring.redis.lettuce.pool.max-idle=100
# 连接池中的最小空闲连接,默认值也是0
spring.redis.lettuce.pool.min-idle=50
# 如果赋值为-1,则表示不限制
spring.redis.lettuce.pool.max-wait=2000
4.FastJsonRedisSerializer编写
package com.lc.springboot.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
//ParserConfig.getGlobalInstance().addAccept("com.demo.redis.entity.Grade");
}
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (null == t) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (null == bytes || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
5.RedisConfig
package com.lc.springboot.config;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
/**
*
* 自定义主键生成策略
* @return
*/
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object o, Method method, Object... objects) {
return method.getName()+ "[" + Arrays.asList(objects).toString() + "]";
}
};
}
/**
* RedisTemplate 配置使用json保存数据而不是用序列化数据
* @param connectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
// 配置redisTemplate
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(connectionFactory);
//template.setKeySerializer(new StringRedisSerializer());//key序列化
//redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//value序列化
template.setDefaultSerializer(new FastJsonRedisSerializer<>(Object.class));//value序列化
template.afterPropertiesSet();
return template;
}
/**
* RedisCacheManger 使用json保存数据的配置
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
.entryTtl(Duration.ofDays(30));
return configuration;
}
}