一、JSR107(复杂性较高)
Java Caching定义了5个核心接口,分别是CachingProvider,ICacheManager,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、创建项目
mybaits mysql jdbc web cache
1、搭建环境mybatis(略)
2、使用默认缓存
默认缓存使用的是: ConcurrentMapCacheManager(CacheManage),可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;后面可以配置redis
为了方便查看sql日志
logging.level.com.zy.springboot10.dao=debug
2.1 开启缓存
@EnableCaching //开启缓存
@MapperScan("com.zy.springboot10.dao")
@SpringBootApplication
public class Springboot10Application {
public static void main(String[] args) {
SpringApplication.run(Springboot10Application.class, args);
}
}
2.2 @Cacheable
2.2.1 service
@Service
public class UserService {
@Autowired
public TbuserMapper tbuserMapper;
/**
*@Cacheable:将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
* CacheMlanager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
* 几个属性:
* cacheNames/value:指定缓存组件的名字;
* key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值
* 编写SpEL;#id;参数id的值 #a0:参数第一个值 #p0 #root.args[0]
* keyGenerator:key的生成器;可以自己指定key的生成器的组件id
* key/keyGenerator:二选一使用
* cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
* condition:指定符合条件的情况下才缓存;
* unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;
* 可以获取到结果进行判断unless="#result==null" / unless="#result.size()==0"
* sync:是否使用异步模式
*
*运行流程
* 1、每次调用 selectTbUserById 方法之前,都会去查找缓存(按照cacheNames),第一次缓存是没有tbUser的,所以会创建缓存
* 2、去cache查找缓存的内容,通过key查找,key默认是方法的参数(没有参数放回SimpleKey.Empty对象)
* 3、没有查到缓存就调用目标方法(selectTbUserId)
* 4、将目标方法的返回结果放到缓存中
*
* 核心流程:
* 1)、使用CacheManager【ConcurrentMapCacheMlanager】按照名字得到cache【ConcurrentMapCache】组件
* 2)、key使用keyGenerator生成的,默认是simpleKeyGenerator
*/
@Cacheable(cacheNames="tbUser",cacheManage="xx") //可以指定配置的cacheManage
public Tbuser selectTbUserById(Integer id){
Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id);
return tbuser;
}
}
2.2.2 测试
@Autowired
UserService userService;
@Test
public void contextLoads() {
Tbuser tbuser = userService.selectTbUserById(47); //service方法只会执行一次
Tbuser tbuser1 = userService.selectTbUserById(47);
}
2.2.3 补充 Cacheable
2.2.3.1 keyGenerator
@Configuration
public class MyCacheConfig {
@Bean("Mygenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+Arrays.asList(params).toString()+"]";
}
};
}
}
@Cacheable(cacheNames="tbUser",keyGenerator = "Mygenerator") //指定key
public Tbuser selectTbUserById(Integer id){
Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id);
return tbuser;
}
2.2.3.2 condition
@Cacheable(cacheNames="tbUser",keyGenerator = "Mygenerator",condition = "#id>1") //id>1的数据缓存,多条件使用and连接
public Tbuser selectTbUserById(Integer id){
Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id);
return tbuser;
}
2.2.3.4 unless
@Cacheable(cacheNames="tbUser",keyGenerator = "Mygenerator",unless = "#id==1") //如果为true,不缓存
public Tbuser selectTbUserById(Integer id){
Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id);
return tbuser;
}
2.3 @ CachePut(清空key对应的缓存,并添加新的缓存)
2.3.1 service
@Cacheable(cacheNames="tbUser")
public Tbuser selectTbUserById(Integer id){
Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id);
return tbuser;
}
@CachePut(cacheNames="tbUser",key = "#tbuser.id") //@CachePut的cacheNames和@Cacheable的cacheNames不一样好像没事,key的值一定需要一样,否则之前缓存的数据就不会清空
public Tbuser updateTbUser(Tbuser tbuser){
tbuserMapper.updateByPrimaryKey(tbuser);
System.out.println(tbuser);
return tbuser; //必须有返回值,否在返回值为null,就变成了了 {47:null}
}
2.3.2 测试
@Test
public void contextLoads() {
Tbuser tbuser = userService.selectTbUserById(47);
Tbuser tbuser1 = userService.selectTbUserById(47);
tbuser1.setUsername("update4");
userService.updateTbUser(tbuser1); //清空key是47的缓存,并添加新的key是47的缓存
Tbuser tbuser2 = userService.selectTbUserById(47);//key存在,直接取缓存
}
2.4 @ CacheEvict(缓存清空,如果删除了数据,将缓存中的某条数据或者指定的缓存组件清空)
2.4.1 sevice
@Cacheable(cacheNames="tbUser")
public Tbuser selectTbUserById(Integer id){
Tbuser tbuser = tbuserMapper.selectByPrimaryKey(id);
return tbuser;
}
@CacheEvict(cacheNames = "tbUser") //cacheNames名字和@Cacheable中的cacheNames名字需要一样,key需要一样,可以不需要key,使用allEntries=true:清空tbUser中的所有的缓存
public void deleteTbUser(Integer id){
tbuserMapper.deleteByPrimaryKey(id);
}
补充:@CacheEvict可以设置方法之前执行还是方法之后执行,默认方法之后执行,如果设置之前执行,无论 deleteTbUser方法执行过程中是否报错,都会清空缓存
2.4.2 测试
Tbuser tbuser = userService.selectTbUserById(53);
Tbuser tbuser1 = userService.selectTbUserById(53);
userService.deleteTbUser(53);
Tbuser tbuser2 = userService.selectTbUserById(53);
2.5 @Caching(定义复杂的缓存规则)
2.5.1 service
@Caching(
cacheable = {
@Cacheable(cacheNames = "tbUser", key = "#tbuser.id"),
},
put = {
@CachePut(cacheNames = "tbUser", key = "#tbuser.name"),
@CachePut(cacheNames = "tbUser", key = "#tbuser.email")}
)
public Tbuser selectTbUserByName(Tbuser tbuser) {
return null;
}
2.6 @CacheConfig(抽取缓存的公共配置)
@CacheConfig(cacheNames = "TbUser") //在类上标明,方法上就不需要写了
@Service
public class UserService {}
补充
//可以指定CacheManager,比如redis
@Autowired
RedisCacheManager redisCacheManager;
//使用缓存管理器得到缓存,进行api调用
public void test(){
//好像使用redisTemplate也是可以的
Cache tbUser = redisCacheManager.getCache("tbUser"); //相当于@Cacheable(cacheNames = "tbUser")中指定的cacheNames
tbUser.put("1","xx");
}
3、使用redis缓存中间件
首先安装安装redis,linux可以使用docker安装,windows可以直接安装
其他的缓存中间件redis、memcached、ehcache;
redis:可以作为数据库、缓存和消息中间件
3.1 引入redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.properties
spring.redis.host=localhost
3.1 操作redis进行crud
测试
@Autowired
RedisTemplate redisTemplate;//k:v 都是对象
@Autowired
StringRedisTemplate stringRedisTemplate; //k:v 都是字符串
/**
* redis常见五大数据类型
* String(字符串):redisTemplate.opsForValue();
* list(列表):redisTemplate.opsForList();
* Set(集合):redisTemplate.opsForSet();
* Hash(散列):redisTemplate.opsForHash();
* zSet(有序集合):redisTemplate.opsForZSet();
*/
@Test
public void test1(){
//字符串操作: append:如果k存在就追加
stringRedisTemplate.opsForValue().append("k","v");
String k = stringRedisTemplate.opsForValue().get("k");
System.out.println(k);
//list操作
stringRedisTemplate.opsForList().leftPushAll("list","1","2");
String s = stringRedisTemplate.opsForList().leftPop("list");
}
保存对象
@Configuration
public class MyRedisConfig {
@Bean("TbUserRedisTemplate")
public RedisTemplate<Object, Tbuser> RedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Tbuser> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<Tbuser>(Tbuser.class) {
});
return redisTemplate;
}
}
测试
@Autowired
RedisTemplate TbUserRedisTemplate;
@Test
public void test1(){
//使用默认的redisTemplate
Tbuser tbuser = userService.selectTbUserById(54);
redisTemplate.opsForSet().add("user",tbuser);
Object user = redisTemplate.opsForSet().pop("user");
//可以正常的取出user,但是redis中保存的序列化的数据乱码;
System.out.println(user);
TbUserRedisTemplate.opsForSet().add("user1",tbuser);
Tbuser user1 = ((Tbuser) TbUserRedisTemplate.opsForSet().pop("user1"));
//可以正常的取出user,但是redis中保存的数据是json数据格式;
System.out.println(user1);
}
3.2 配置redis,直接使用默认缓存的方法使用redis就可以了,数据自动缓存在redis中,默认保存的数据都是k-v都是object
3.3 原理
1、引入了redis的starter,cacheManager变为RedisCacheManager;
2、默认创建的RedisCacheManager 操作 redis的时候使用的是 RedisTemplate<Object,Object>
3、RedisTemplate<Object,Object>是默认使用jdk的序列化机制
4、自定义CacheManager
@Configuration
public class MyRedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory){
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();}
}
自定义RedisCacheConfiguration(序列化机制未成功)
@Bean //如果有多个CacheManager,在某一个CacheManager上指定@Primary(主配置)
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的 key 会使用这个
this.getRedisCacheConfigurationMap() // 指定 key 策略
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
redisCacheConfigurationMap.put("UserInfoList", this.getRedisCacheConfigurationWithTtl(3000));
redisCacheConfigurationMap.put("UserInfoListAnother", this.getRedisCacheConfigurationWithTtl(18000));
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
//是否允许value值为空
//redisCacheConfiguration.getAllowCacheNullValues();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}