• spring boot redis 缓存(cache)集成



    Spring Boot 集成教程


    概述

    本文介绍spring boot项目集成redis缓存的过程。

    redis是一个开源的内存NOSQL数据库,在web开发中主要被用于数据缓存。一般在高并发的情况下,web服务器接受访问时,直接从数据库加载是慢的,需要把常用数据缓存到redis中,提高加载速度和并发能力。

    项目内容

    创建一个spring boot项目,配置redis各相关 bean,实现几个接口,通过两种方式测试redis缓存:

    • 以注解方式自动缓存
    • RedisTemplate手动访问redis服务器

    要求

    • 安装redis服务器,参考官网文档,如没有linux系统可虚拟机安装
    • JDK1.8或更新版本
    • Eclipse开发环境

    如没有开发环境,可参考前面章节:[spring boot 开发环境搭建(Eclipse)]。

    项目创建

    创建spring boot项目

    打开Eclipse,创建spring boot的spring starter project项目,选择菜单:File > New > Project ...,弹出对话框,选择:Spring Boot > Spring Starter Project,在配置依赖时,勾选webredis,完成项目创建。

    image

    项目依赖

    需要用到commons-pool2库,在pom.xml中添加依赖

    		<dependency>
    		    <groupId>org.apache.commons</groupId>
    		    <artifactId>commons-pool2</artifactId>
    		</dependency>
    

    项目配置

    application.properties文件中配置redis服务器的连接

    ## REDIS (RedisProperties)
    # Redis数据库索引(默认为0)
    spring.redis.database=0  
    # Redis服务器地址
    spring.redis.host=192.168.0.99
    # Redis服务器连接端口
    spring.redis.port=6379  
    # Redis服务器连接密码(默认为空)
    spring.redis.password=  
    # 连接池最大连接数(使用负值表示没有限制)
    spring.redis.lettuce.pool.max-active=8  
    # 连接池最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.lettuce.pool.max-wait=-1
    # 连接池中的最大空闲连接
    spring.redis.lettuce.pool.max-idle=8  
    # 连接池中的最小空闲连接
    spring.redis.lettuce.pool.min-idle=0
    
    

    代码实现

    项目目录结构如下图,我们添加了几个类,下面将详细介绍。

    image

    Redis Java配置(RedisConfig.java)

    首先使用@EnableCaching开启以注解方式使用缓存。

    然后配置redis相关的bean

    • RedisTemplate - 访问redis的bean,用于手动访问redis服务器
    • 缓存管理器 - 注解方式使用缓存的配置
    • KeyGenerator - 自定义缓存key的生成
    • Json序列化 - Json对象被缓存时的序列化
    /**
     * @description redis配置  配置序列化方式以及缓存管理器 
     */
    @EnableCaching // 开启缓存
    @Configuration
    @AutoConfigureAfter(RedisAutoConfiguration.class)
    public class RedisConfig {
    	
        /**
         * 配置自定义redisTemplate
         *
         * @param connectionFactory
         * @return
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(connectionFactory);
            template.setValueSerializer(jackson2JsonRedisSerializer());
            //使用StringRedisSerializer来序列化和反序列化redis的key值
            template.setKeySerializer(new StringRedisSerializer());
            template.setHashKeySerializer(new StringRedisSerializer());
            template.setHashValueSerializer(jackson2JsonRedisSerializer());
            template.afterPropertiesSet();
            return template;
        }
    
        /**
         * json序列化
         * @return
         */
    	@Bean
        public RedisSerializer<Object> jackson2JsonRedisSerializer() {
            //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
            Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
    
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            serializer.setObjectMapper(mapper);
            return serializer;
        }
    
        /**
         * 配置缓存管理器
         * @param redisConnectionFactory
         * @return
         */
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
            // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
            // 设置缓存的默认过期时间,也是使用Duration设置
            config = config.entryTtl(Duration.ofMinutes(1))
                    // 设置 key为string序列化
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                    // 设置value为json序列化
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
                    // 不缓存空值
                    .disableCachingNullValues();
    
            // 设置一个初始化的缓存空间set集合
            Set<String> cacheNames = new HashSet<>();
            cacheNames.add("timeGroup");
            cacheNames.add("user");
    
            // 对每个缓存空间应用不同的配置
            Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
            configMap.put("timeGroup", config);
            configMap.put("user", config.entryTtl(Duration.ofSeconds(120)));
    
            // 使用自定义的缓存配置初始化一个cacheManager
            RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                    // 一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
                    .initialCacheNames(cacheNames)
                    .withInitialCacheConfigurations(configMap)
                    .build();
            return cacheManager;
        }
        
        /**
         * 缓存的key是 包名+方法名+参数列表
         */
        @Bean
        public KeyGenerator keyGenerator() {
            return (target, method, objects) -> {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append("::" + method.getName() + ":");
                for (Object obj : objects) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            };
        }
    
    }
    
    

    添加实体类 User

    
    public class User {
    	private long id;
    	private String nickname;
    	private String mobile;
    	
    	@JsonProperty(access = Access.WRITE_ONLY) //在输出的Json数据中隐藏密码,只能输入不输出
    	private String password;
    	private String role;
    	
    	public User(long id, String nickname, String mobile, String password, String role) {
    		this.id = id;
    		this.nickname = nickname;
    		this.mobile = mobile;
    		this.password = password;
    		this.role = role;
    	}
    
    	public User() {
    		super();
    	}
    	
    	public String getNickname() {
    		return nickname;
    	}
    	public void setNickname(String nickname) {
    		this.nickname = nickname;
    	}
    	public long getId() {
    		return id;
    	}
    	public void setId(long id) {
    		this.id = id;
    	}
    	public String getMobile() {
    		return mobile;
    	}
    	public void setMobile(String mobile) {
    		this.mobile = mobile;
    	}
    	public String getPassword() {
    		return password;
    	}
    	public void setPassword(String password) {
    		this.password = password;
    	}
    	public String getRole() {
    		return role;
    	}
    	public void setRole(String role) {
    		this.role = role;
    	}
    }
    

    常用的缓存注解

    • @Cacheable - 表明对应方法的返回结果可以被缓存,首次调用后,下次就从缓存中读取结果,方法不会再被执行了。
    • @CachePut - 更新缓存,方法每次都会执行
    • @CacheEvict - 清除缓存,方法每次都会执行

    添加User的服务层

    因为主要的业务逻辑在服务层实现,一般会把缓存注解加在服务层的方法上。

    下面几个服务层的方法会加缓存注解:

    • getUserById - 方法的返回结果会被缓存到redis,使用注解@Cacheable
    • updateUserNickname - 原始数据被更新了,废弃缓存数据,使用注解@CacheEvict

    UserSevice.java 接口

    public interface UserService {
    	
        public User getUserById(long userId);
        public User updateUserNickname(long userId, String nickname);
    }
    

    UserServiceImpl.java 实现类

    
    @Service("userService")
    public class UserServiceImpl implements UserService {
        
        private static final org.slf4j.Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
        
        private User user = new User(1l, "abc1", "13512345678", "123456", "role-user");
        
        @Cacheable(value = "user", key= "#userId")
        @Override
        public User getUserById(long userId) {
            
            log.info("加载用户信息");
            return user;
        }
        
        @CacheEvict(value = "user", key= "#userId")
        @Override
        public User updateUserNickname(long userId, String nickname) {
            
            user.setNickname(nickname);
            
            return user;
        }
    }
    

    添加User的控制层

    
    @RestController
    @EnableAutoConfiguration
    @RequestMapping("/user")
    public class UserController {
    	
    	// 注入service类
        @Resource
        private UserService userService;
        
        // 注入RedisTemplate
        @Resource
        private RedisTemplate<String, Object> redis;
    	
        // 读取用户信息,测试缓存使用:除了首次读取,接下来都应该从缓存中读取
        @RequestMapping(value="{id}", method=RequestMethod.GET, produces="application/json")
        public User getUser(@PathVariable long id) throws Exception {
        	
            User user = this.userService.getUserById(id);
            
            return user;
        }
        
        // 修改用户信息,测试删除缓存
        @RequestMapping(value = "/{id}/change-nick", method = RequestMethod.POST, produces="application/json")
        public User changeNickname(@PathVariable long id) throws Exception{
            
            String nick = "abc-" + Math.random();
            User user = this.userService.updateUserNickname(id, nick);
            
            return user;
        }
        
        // 使用RedisTemplate访问redis服务器
        @RequestMapping(value="/redis", method=RequestMethod.GET, produces="application/json")
        public String redis() throws Exception {
            
            // 设置键"project-name",值"qikegu-springboot-redis-demo"
            redis.opsForValue().set("project-name", "qikegu-springboot-redis-demo");
            String value = (String) redis.opsForValue().get("project-name");
            
            return value;
        }
    }
    

    运行

    Eclipse左侧,在项目根目录上点击鼠标右键弹出菜单,选择:run as -> spring boot app 运行程序。 打开Postman访问接口,
    同时监控redis服务器。

    监控redis服务器,使用redis-cli命令连上服务器,然后使用monitor命令开始监控:

    image

    运行结果如下:

    获取用户信息

    image

    redis中的数据,可以看到数据通过SET指令保存进redis了

    image

    多次获取用户信息,可以看到通过GET指令从redis中读取缓存

    image

    修改用户信息

    image

    redis中的缓存被删除了

    image

    测试使用RedisTemplate访问redis服务器

    image

    redis中的数据变化

    image

    总结

    完整代码

  • 相关阅读:
    三种方法处理文字中的空格
    text——文本属性大全
    font——文字属性大全
    padding和margin——内边距和外边距
    background——背景属性
    C# 解析excel时,字段内有内容,却读取不到的解决方法
    jqprint 打印分页
    pre标签 首行会自动换行解决方案
    正则表达式 清除所有标签的属性
    针对安卓微信浏览器网页 置顶悬浮框浮动固定 的问题
  • 原文地址:https://www.cnblogs.com/jinbuqi/p/11038731.html
Copyright © 2020-2023  润新知