Spring Boot----缓存

一、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");
}

缓存对象

配置,spring-boot-starter-data-redis需要使用RedisTemplate

@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;
    }

  

 

posted @ 2019-08-18 22:32  小名的同学  阅读(360)  评论(0编辑  收藏  举报