springBoot与缓存

JSR107

java Caching五个核心接口:

CachingProvider:管理Cachemanager

CacheManager:定义了创建、配置、获取、管理控制多个Cache

Cache:类似map只被一个CacheManager拥有

Entry:是一个存储在Cache中的键值对

Expiry:缓存有效期

引入jar包

<dependency>
	<groupId>javax.cache</groupId>
	<artifactId>cache-api</artifactId>
</dependency>

spring缓存抽象

Cache:缓存

CacheManager 缓存管理,管理Cache

@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存(查询方法)

@CacheEvict:清空缓存(删除方法)

@CachePut:保证方法被调用,有希望结果被缓存(更新方法)

@EnableCaching:开启基于注解的缓存(启动类)

keyGenerator缓存数据时key生成策略

serialize缓存数据时value序列化策略

使用缓存

1.开启缓存

启动类

@SpringBootApplication
@MapperScan("com.zhuoyue.mapper")
//开启缓存
@EnableCaching
public class ShoppingdemoApplication {

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

}

2.给方法开启缓存

@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存(查询方法)

​ cacheNames/value:指定缓存的名字 ,将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个

​ key:缓存数据时使用的key,可以用它指定,默认使用方法的参数

​ keyGenerator:key的生成器和key互斥二选一

​ cacheManager:缓存管理器

​ condition:指定符合条件的情况下才缓存还可以像key一样使用Spel

​ unless:满足条件不缓存

​ sync:是否异步

@CacheEvict:清空缓存(删除方法)

@CachePut:保证方法被调用,有希望结果被缓存(更新方法)

 @Override
    @Cacheable(cacheNames = "emp")
    public Page<DeptPO> findAll(PageDeptParam param) {
        Page<DeptPO> page = new Page<>(param.getPageIndex(), param.getPageSize());
        return lambdaQuery()
                .like(param.getDname()!=null,DeptPO::getDname,param.getDname())
                .like(param.getLoc()!=null,DeptPO::getLoc,param.getLoc())
                .le(param.getEndTime()!=null, DeptPO::getCreateTime,param.getEndTime())
                .ge(param.getBeginTime()!=null,DeptPO::getCreateTime,param.getBeginTime())
                .page(page);
    }

缓存原理

自动配置原理

@Cacheable运行原理

@Cacheabled的其他属性

key的使用
 @Override
    @Cacheable(cacheNames = "emp",key="#p0.pageIndex")
    public Page<DeptPO> findAll(PageDeptParam param) {
        Page<DeptPO> page = new Page<>(param.getPageIndex(), param.getPageSize());
        return lambdaQuery()
                .like(param.getDname()!=null,DeptPO::getDname,param.getDname())
                .like(param.getLoc()!=null,DeptPO::getLoc,param.getLoc())
                .le(param.getEndTime()!=null, DeptPO::getCreateTime,param.getEndTime())
                .ge(param.getBeginTime()!=null,DeptPO::getCreateTime,param.getBeginTime())
                .page(page);
    }
keyGenerator

定义:

使用:

condition

指定符合条件的情况下才缓存

 @Override
    @Cacheable(cacheNames = "emp",condition="#p0.pageIndex>1")
    public Page<DeptPO> findAll(PageDeptParam param) {
        Page<DeptPO> page = new Page<>(param.getPageIndex(), param.getPageSize());
        return lambdaQuery()
                .like(param.getDname()!=null,DeptPO::getDname,param.getDname())
                .like(param.getLoc()!=null,DeptPO::getLoc,param.getLoc())
                .le(param.getEndTime()!=null, DeptPO::getCreateTime,param.getEndTime())
                .ge(param.getBeginTime()!=null,DeptPO::getCreateTime,param.getBeginTime())
                .page(page);
    }
unless

满足条件不缓存

 @Override
    @Cacheable(cacheNames = "emp",condition="#p0.pageIndex>1",unless="#p0.pageIndex>1")
    public Page<DeptPO> findAll(PageDeptParam param) {
        Page<DeptPO> page = new Page<>(param.getPageIndex(), param.getPageSize());
        return lambdaQuery()
                .like(param.getDname()!=null,DeptPO::getDname,param.getDname())
                .like(param.getLoc()!=null,DeptPO::getLoc,param.getLoc())
                .le(param.getEndTime()!=null, DeptPO::getCreateTime,param.getEndTime())
                .ge(param.getBeginTime()!=null,DeptPO::getCreateTime,param.getBeginTime())
                .page(page);
    }
sync 是否异步

@CachePut 更新缓存

    @Override
    @CachePut(cacheNames="dept",key = "#p0.id")
    public DeptPO merge(MergeDeptParam param) {
        lambdaUpdate()
                .set(param.getDname()!=null,DeptPO::getDname,param.getDname())
                .set(param.getLoc()!=null,DeptPO::getLoc,param.getLoc())
                .eq(DeptPO::getId,param.getId())
                .update();
        return lambdaQuery().eq(DeptPO::getId,param.getId()).one();
    }

将修改后的数据放入缓存,如果key一样,查询时就去找缓存

@CacheEvict

根据指定cacheNames和指定key删除缓存

  @Override
    @CacheEvict(cacheNames = "dept",key = "#p0")
    public void del(Long id) {
        System.out.println("删除缓存数据!");
    }

allEntity=true清楚所有缓存

  @Override
    @CacheEvict(cacheNames = "dept",key = "#p0",allEntity=true)
    public void del(Long id) {
        System.out.println("删除缓存数据!");
    }

beforeInvocation = true方法之前清楚缓存,默认方法之后

  @Override
    @CacheEvict(cacheNames = "dept",key = "#p0",beforeInvocation = true)
    public void del(Long id) {
        System.out.println("删除缓存数据!");
    }

@Caching

可配置缓存规则

@Override
@Caching(
        cacheable = {
                @Cacheable(cacheNames = "dept")
        },
        //方法运行之后
        put = {
                @CachePut(cacheNames = "dept",key = "#result.id")
        }
)
public DeptPO merge(MergeDeptParam param) {
    lambdaUpdate()
		.set(param.getDname()!=null,DeptPO::getDname,param.getDname())
            .set(param.getLoc()!=null,DeptPO::getLoc,param.getLoc())
            .eq(DeptPO::getId,param.getId())
            .update();
    return lambdaQuery()
        .eq(DeptPO::getId,param.getId())
        .one();
}

@CacheConfig

类注解

放到类上可以指定统一的cacheNames

@Service
@CacheConfig(cacheNames = "dept")
public class DeptServiceImpl extends ServiceImpl<DeptMapper, DeptPO> implements DeptService {
}

集成redis

引入redis的jar包

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

写配置

spring:
  redis:
    host: 127.0.0.1
    port: 6379

此时已经集成了redis的缓存,但是无法转换mybatis-plus的page

配置类

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Administrator
 * @date 2022/1/10
 */

@Configuration
@EnableCaching
public class RedisConfig {

    /**
     * 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)
     * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值
     *
     * @return
     */
    /**
     * 注解形式
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                this.getRedisCacheConfigurationWithTtl(300), // 默认策略,未配置的 key 会使用这个
                this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        redisCacheConfigurationMap.put("getRolePermissions", this.getRedisCacheConfigurationWithTtl(300));
        redisCacheConfigurationMap.put("getUser", this.getRedisCacheConfigurationWithTtl(60));
        redisCacheConfigurationMap.put("getProject", this.getRedisCacheConfigurationWithTtl(60));
        redisCacheConfigurationMap.put("getTeam", this.getRedisCacheConfigurationWithTtl(60));
//        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);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class,
                new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        om.registerModule(timeModule);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }

    /**
     * 编程形式
     *
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        // 创建一个模板类
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        // 将刚才的redis连接工厂设置到模板类中
        template.setConnectionFactory(factory);
        // 设置key的序列化器
        template.setKeySerializer(new StringRedisSerializer());
        // 设置value的序列化器
        //使用Jackson 2,将对象序列化为JSON
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //json转对象类,不设置默认的会将json转成hashmap
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        //LocalDatetime序列化
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class,
                new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(timeModule);


        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);

        return template;
    }
}

然后,可以用缓存注解开发。