美文网首页
SpringCache使用Redis来缓存热点数据

SpringCache使用Redis来缓存热点数据

作者: 砒霜拌辣椒 | 来源:发表于2020-08-10 16:34 被阅读0次

    在很多应用场景中通常是获取前后相同或更新不频繁的数据,比如访问产品信息数据、网页数据。如果没有使用缓存,则访问每次需要重复请求数据库,这会导致大部分时间都耗费在数据库查询和方法调用上,因为数据库进行I/O操作非常耗费时间,这时就可以利用Spring Cache来解决。

    Spring Cache 是Spring提供的一整套缓存解决方案。它本身并不提供缓存实现,而是提供统一的接口和代码规范、配置、注解等,以便整合各种Cache方案,使用户不用关心Cache的细节。

    Spring Cache作用在方法上。当调用一个缓存方法时,会把方法参数和返回结果作为一个“键值对”(key/value)存放在缓存中,下次用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache时,要保证在缓存的方法和方法参数相同时返回相同的结果。

    1、声明式注解

    注解 说明
    @EnableCaching 启动类声明,用来开启缓存
    @Cacheable 可以作用在类和方法上,以键值对的方式缓存类或方法的返回值
    @CachePut 方法被调用,然后结果被缓存
    @CacheEvict 清空缓存
    @Caching 用来组合多个注解标签
    • @Cacheable会先查询是否已有缓存,没有则再执行方法,将返回值缓存起来,key可以有默认策略和自定义策略。用于查询热点数据。
    • @CachePut每次都会执行方法,并将方法的返回值缓存。用于更新数据。

    2、Maven依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
    

    3、应用缓存

    3.1、Redis配置类

    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport {
        /**
         * 缓存管理器,适合2.x版本
         *
         * @param redisConnectionFactory
         * @return
         */
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
            // 对于指定的cacheNames设置缓存的过期时长
            Map<String, RedisCacheConfiguration> cacheConfigMap = new HashMap<>();
            cacheConfigMap.put("users", this.getCacheConfigurationWithTtl(2 * 60 * 60));
    
            // 构建CacheManager对象,指定的cacheNames过期时长为2小时,其它默认为1小时
            return RedisCacheManager.builder(redisConnectionFactory)
                    .withInitialCacheConfigurations(cacheConfigMap)
                    .cacheDefaults(this.getCacheConfigurationWithTtl(1 * 60 * 60))
                    .build();
        }
    
        /**
         * 设置缓存过期时长
         *
         * @param seconds 过期时长
         * @return
         */
        private RedisCacheConfiguration getCacheConfigurationWithTtl(long seconds) {
            // 序列化设置
            Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 2.1.x
    //        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); // 2.2.x
            jacksonSeial.setObjectMapper(om);
    
            // 基本配置
            RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                    .defaultCacheConfig()
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSeial))
                    // 不缓存null
                    .disableCachingNullValues()
                    // 缓存数据保存时长
                    .entryTtl(Duration.ofSeconds(seconds));
            return redisCacheConfiguration;
        }
    }
    

    这里主要配置了序列化方式和对于不同缓存的过期时长设置。

    3.2、数据层

    建表语句

    DROP TABLE IF EXISTS user;
    CREATE TABLE user (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        name varchar(64) COMMENT '姓名',
        age int(4) COMMENT '年龄',
        PRIMARY KEY (id)
    )COMMENT = '用户表';
    
    insert into user values (1, '赵晓斌', 28);
    insert into user values (2, '李白', 22);
    insert into user values (3, '宋老三', 30);
    

    实体类,一定要实现Serializable接口。

    @Data
    public class User implements Serializable {
        private static final long serialVersionUID = 5485617646232613710L;
        private Long id;
        private String name;
        private int age;
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            User user = (User) o;
            return age == user.age &&
                    Objects.equals(id, user.id) &&
                    Objects.equals(name, user.name);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(id, name, age);
        }
    }
    

    缓存层接口

    public interface UserCacheService {
        User findById(Long id);
        User updateUserById(User user);
        void deleteById(Long id);
    }
    

    这里采用Mybatis来操作数据库,具体操作和配置不在这里展开。

    3.3、缓存查询数据

    @Service
    @CacheConfig(cacheNames = "users") // 缓存名称,也是key前缀
    public class UserCacheServiceImpl implements UserCacheService {
        @Autowired
        private UserDao userDao;
    
        @Override
        @Cacheable(key = "#p0") // 取第一个参数作为key的一部分,比如users::1
        public User findById(Long id) {
            return userDao.findById(id);
        }
    }
    

    根据key值查询缓存,如果没有则执行方法查询数据库,将结果缓存起来,下次同样的key查询时直接从缓存取值返回,不再执行方法。

    • @CacheConfig注解指定缓存key的前缀为users
    • @Cacheable等注解指定key时,使用SpEL表达式:
      • #p0:表示取第一个参数。
      • #p0.id:表示取第一个参数的id属性。
      • #id:表示取参数id。

    3.4、更新缓存数据

    @Override
    @CachePut(key = "#p0.id") // 访问bean属性
    @Transactional
    public User updateUserById(User user) {
        userDao.updateUserById(user);
        return user;
    }
    

    每次更新完数据库后,同步更新缓存中的数据。需要保持和缓存时的key相同。

    3.5、删除缓存数据

    @Override
    @CacheEvict(key = "#id") // 可以用形参名表示key,allEntries = true 则代表 users:: 开头的键全部删除
    @Transactional
    public void deleteById(Long id) {
        userDao.deleteById(id);
    }
    

    在删除完数据库的数据后,同步删除缓存中的数据。也是需要保持和缓存时的key相同。
    如果需要批量删除某一类key,只需要把@CacheEvict注解中的allEntries属性设为true,那就会清空所有缓存数据(仅限于同一个cacheNames也就是@CacheConfig注解指定的key前缀)。

    4、测试验证

    @RestController
    public class CacheController {
        @Autowired
        private UserCacheService userCacheService;
    
        @RequestMapping("/findById")
        public User findById(Long id) {
            return userCacheService.findById(id);
        }
    
        @RequestMapping("/updateUserById")
        public void updateUserById(User user) {
            userCacheService.updateUserById(user);
        }
    
        @RequestMapping("/deleteById")
        public void deleteById(Long id) {
            userCacheService.deleteById(id);
        }
    }
    
    1. 在第一次查询时,可以看到有查询数据库的日志输出。
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@608e7bd3] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4945f51a] will not be managed by Spring
    ==>  Preparing: select id, name, age from user where id = ? 
    ==> Parameters: 3(Long)
    <==    Columns: id, name, age
    <==        Row: 3, ares, 33
    <==      Total: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@608e7bd3]
    
    1. 查询Redis,可以看到已经有数据缓存了,过期时间就是前面配置中设置的2小时。
    127.0.0.1:9427> keys *
    users::3
    127.0.0.1:9427> get users::3
    ["net.zhaoxiaobin.cache.domain.User",{"id":3,"name":"ares","age":22}]
    127.0.0.1:9427> ttl users::3
    7185
    
    1. 再次查询,发现依旧可以得到相同结果,但没有查询数据库的日志输出,说明缓存生效了。

    2. 修改字段更新数据库,然后再查询,可以看到缓存数据也被更新。

    3. 删除一条数据后,再根据id查询就得到空,说明缓存中的这条数据也已被删除了。

    参考链接

    代码地址

    相关文章

      网友评论

          本文标题:SpringCache使用Redis来缓存热点数据

          本文链接:https://www.haomeiwen.com/subject/yvpjdktx.html