zoukankan      html  css  js  c++  java
  • 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");
    }
    

    缓存对象

    配置(让对象以json缓存到redis中)

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

      

  • 相关阅读:
    U1. 广度优先搜索(BFS)和 广度优先搜索(DFS)
    C5. Spring 服务的注册与发现(Spring Cloud Eureka)
    S3. Android 消息推送
    S2. Android 常用控件
    S12. Android 检查更新功能实现
    S1. Android 功能大全
    B9 Concurrent 重入锁(ReentrantLock)
    117.dom2事件
    106.事件的传播机制
    105.事件对象及兼容处理
  • 原文地址:https://www.cnblogs.com/yanxiaoge/p/11374310.html
Copyright © 2011-2022 走看看