maven整合@data注解_SpringBoot2.x教程——NoSQL之SpringBoot整合Redis

一. Spring Boot整合Redis实现

1. Redis简介

Redis是一个缓存,消息中间件及具有丰富特性的键值存储系统。Spring Boot为Redis的客户端Jedis提供了自动配置实现,Spring Data Redis提供了在它之上的抽象,spring-boot-starter-redis'Starter'为我们提供了必要的依赖。

2. 环境配置

  • Springboot 2.2.5;
  • Redis 3.2.x;
  • Redis可视化工具Redis Desktop Manager

3. 创建web项目

我们按照之前的经验,创建一个web程序,并将之改造成Spring Boot项目,具体过程略。

489078c01ed4455a908dbe26076148e1

4. 添加依赖

org.springframework.boot            spring-boot-starter-data-jpa        org.springframework.boot            spring-boot-starter-data-redis        org.apache.commons            commons-pool2        redis.clients            jedis            2.7.3com.fasterxml.jackson.datatype            jackson-datatype-jsr310        mysql            mysql-connector-java            runtimecom.alibaba            druid            1.1.10

5. 添加yml配置文件

SpringBoot集成Redis主要是使用RedisTemplate类进行操作,但是在SpringBoot2.0以后,底层默认访问的不再是Jedis而是lettuce。

5.1 jedis客户端和lettuce客户端的区别

  • jedis采用的是直连redis server,在多线程之间公用一个jedis实例,是线程不安全的。想要避免线程不安全,可以使用连接池pool,这样每个线程单独使用一个jedis实例。但是线程过多时,带来的是redis server的负载较大,有点类似BIO模式。
  • lettuce采用netty连接redis server,实例在多个线程间共享,不存在线程不安全的情况,这样可以减少线程数量。当然在特殊情况下,lettuce也可以使用多个实例,有点类似NIO模式。

5.2 yml配置文件

spring:  datasource:    username: root    password: syc    url: jdbc:mysql://localhost:3306/db4?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC    driver-class-name: com.mysql.jdbc.Driver  jpa:    show-sql: true    hibernate:      ddl-auto: update  redis:    host: 127.0.0.1    port: 6379    password:    timeout: 3600ms #超时时间    lettuce: #若是在配置中配置了lettuce的pool属性,那么必须在pom.xml文件中加入commons-pool2的依赖。      pool:        max-active: 8 #最大连接数        max-idle: 8 #最大空闲连接 默认8        max-wait: -1ms #默认-1 最大连接阻塞等待时间        min-idle: 0 #最小空闲连接#    jedis:#      pool:#        max-active: 8 #最大连接数#        max-idle: 8 #最大空闲连接 默认8#        max-wait: -1ms #默认-1 最大连接阻塞等待时间#        min-idle: 0 #最小空闲连接

6. 创建RedisConfig配置类

6.1 RedisTemplate自动装配

在SpringBoot中,已经自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。下面是SpringBoot中关于RedisTemplate自动装配的源码:

@Configuration@ConditionalOnClass(RedisOperations.class)@EnableConfigurationProperties(RedisProperties.class)@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })public class RedisAutoConfiguration {    @Bean    @ConditionalOnMissingBean(name = "redisTemplate")    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory)            throws UnknownHostException {        RedisTemplate template = new RedisTemplate<>();        template.setConnectionFactory(redisConnectionFactory);        return template;    }    @Bean    @ConditionalOnMissingBean    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)            throws UnknownHostException {        StringRedisTemplate template = new StringRedisTemplate();        template.setConnectionFactory(redisConnectionFactory);        return template;    }}

从源码中可以看出,我们开发时会存在2个问题:

  • RdisTemplate的泛型是,我们在进行缓存时写代码不是很方便,因为一般我们的key是String类型,所以我们需要一个的泛型。
  • RedisTemplate没有设置数据存储在Redis时,Key和Value的序列化方式(采用默认的JDK序列化方式)。

那么如何解决上述两个问题呢?

@ConditionalOnMissing注解:如果Spring容器中已经定义了id为redisTemplate的Bean,那么自动装配的RedisTemplate不会实例化。因此我们可以写一个配置类,配置Redisemplate。若未自定义RedisTemplate,默认会对key进行jdk序列化。

6.2 RedisSerializer序列化器

当我们利用StringRedisSerializer,Jackson2JsonRedisSerializer和JdkSerializationRedisSerializer进行序列化时,对同一个数据进行序列化前后的结果如下表:

cd5863b2791b4d7eb4573c3d3cfaa381

本案例中,我们对于Key采用stringRedisSerializer;而对于Value我们采用jackson2JsonRedisSerializer的序列化方式。

ObjectMapper是Jackson操作的核心,Jackson所有的json操作都是在ObjectMapper中实现的。om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);设置所有访问权限以及所有的实际类型都可序列化和反序列化om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);Jackson的ObjectMapper.DefaultTyping.NON_FINAL属性的作用。

6.3 Jackson序列化特性

在JDK1.8中的时间类,采用了一套了新的API,但是在反序列化中,会出现异常。

com.fasterxml.jackson.databind.exc.InvalidDefinitionException:Cannot construct instance of  java.time.LocalDate (no Creators, like default construct, exist):cannot deserialize from Object value (no delegate- or property-based Creator)

在SpringBoot中的解决方案:

在MAVEN中加入jackson-datatype-jsr310依赖。

com.fasterxml.jackson.datatype  jackson-datatype-jsr310

配置Configuration中的ObjectMapper。

@Beanpublic ObjectMapper serializingObjectMapper() {  ObjectMapper objectMapper = new ObjectMapper();  objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);  objectMapper.registerModule(new JavaTimeModule());  return objectMapper;}

6.4 自定义RedisTemplate

package com.yyg.boot.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.SerializationFeature;import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;import org.springframework.beans.factory.annotation.Value;import org.springframework.cache.CacheManager;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.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.RedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;/** * @Author 一一哥Sun * @Date Created in 2020/4/8 * @Description Description */@Configurationpublic class RedisConfig {    @Value("${spring.redis.timeout}")    private Duration timeToLive = Duration.ZERO;    /**     * 由于原生的redis自动装配,在存储key和value时,没有设置序列化方式,故自己创建redisTemplate实例     */    @Bean    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {        RedisTemplate template = new RedisTemplate<>();        // 配置连接工厂        template.setConnectionFactory(factory);        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)        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);        jacksonSeial.setObjectMapper(om);        // 值采用json序列化        template.setValueSerializer(jacksonSeial);        //使用StringRedisSerializer来序列化和反序列化redis的key值        template.setKeySerializer(new StringRedisSerializer());        // 设置hash key 和value序列化模式        template.setHashKeySerializer(new StringRedisSerializer());        template.setHashValueSerializer(jacksonSeial);        template.afterPropertiesSet();        return template;    }    /**     * 解决jdk1.8中新时间API的序列化时出现com.fasterxml.jackson.databind.exc.InvalidDefinitionException的问题     */    @Bean    public ObjectMapper serializingObjectMapper() {        ObjectMapper objectMapper = new ObjectMapper();        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);        objectMapper.registerModule(new JavaTimeModule());        return objectMapper;    }    @Bean    public CacheManager cacheManager(RedisConnectionFactory factory) {        RedisSerializer redisSerializer = new StringRedisSerializer();        Jackson2JsonRedisSerializer 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 config = RedisCacheConfiguration.defaultCacheConfig()                .entryTtl(timeToLive)                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))                .disableCachingNullValues();        return  RedisCacheManager.builder(factory)                .cacheDefaults(config)                .build();    }}

7. 创建SpringContextUtil工具类

package com.yyg.boot.util;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Service;/** * @Author 一一哥Sun * @Date Created in 2020/4/8 * @Description Description */@Servicepublic class SpringContextUtil implements ApplicationContextAware {    private static ApplicationContext context;    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        SpringContextUtil.context = applicationContext;    }    public static  T getBean(String name, Class requiredType) {        return context.getBean(name, requiredType);    }}

8. 创建RedisUtil工具类

RedisTemplate模板类可以对Redis进行添加,删除,设置缓存过期时间等设置。RedisTemplate中主要的API是:opsForValue()集合使用说明

  • 1). set(K key,V value) 新建缓存
    redisTemplate.opsForValue().set("key","value");
  • 2). get(Object key) 获取缓存
    redisTemplate.opsForValue().get("key");
package com.yyg.boot.util;import lombok.extern.slf4j.Slf4j;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.util.CollectionUtils;import java.util.concurrent.TimeUnit;/** * @Author 一一哥Sun * @Date Created in 2020/4/8 * @Description Description */@Slf4jpublic class RedisUtil {    //@Autowired    //private static RedisTemplate redisTemplate;    private static final RedisTemplate redisTemplate = SpringContextUtil.getBean("redisTemplate", RedisTemplate.class);    /**********************************************************************************     * redis-公共操作     **********************************************************************************/    /**     * 指定缓存失效时间     *     * @param key  键     * @param time 时间(秒)     * @return     */    public static boolean expire(String key, long time) {        try {            if (time > 0) {                redisTemplate.expire(key, time, TimeUnit.SECONDS);            }            return true;        } catch (Exception e) {            log.error("【redis:指定缓存失效时间-异常】", e);            return false;        }    }    /**     * 根据key 获取过期时间     *     * @param key 键 不能为null     * @return 时间(秒) 返回0代表为永久有效;如果该key已经过期,将返回"-2";     */    public static long getExpire(String key) {        return redisTemplate.getExpire(key, TimeUnit.SECONDS);    }    /**     * 判断key是否存在     *     * @param key 键     * @return true 存在 false不存在     */    public static boolean exists(String key) {        try {            return redisTemplate.hasKey(key);        } catch (Exception e) {            log.error("【redis:判断{}是否存在-异常】", key, e);            return false;        }    }    /**********************************************************************************     * redis-String类型的操作     **********************************************************************************/    /**     * 普通缓存放入     *     * @param key   键     * @param value 值     * @return true成功 false失败     */    public static boolean set(String key, Object value) {        try {            redisTemplate.opsForValue().set(key, value);            return true;        } catch (Exception e) {            log.error("【redis:普通缓存放入-异常】", e);            return false;        }    }    /**     * 普通缓存放入并设置时间     *     * @param key   键     * @param value 值     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期     * @return true成功 false 失败     */    public static boolean set(String key, Object value, long time) {        try {            if (time > 0) {                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);            } else {                set(key, value);            }            return true;        } catch (Exception e) {            log.error("【redis:普通缓存放入并设置时间-异常】", e);            return false;        }    }    /**     * 递增     *     * @param key   键     * @param delta 要增加几(大于0)     * @return     */    public static long incr(String key, long delta) {        if (delta < 0) {            throw new RuntimeException("递增因子必须大于0");        }        return redisTemplate.opsForValue().increment(key, delta);    }    /**     * 递减     *     * @param key   键     * @param delta 要减少几(小于0)     * @return     */    public static long decr(String key, long delta) {        if (delta < 0) {            throw new RuntimeException("递减因子必须大于0");        }        return redisTemplate.opsForValue().increment(key, -delta);    }    /**     * 删除缓存     *     * @param key 可以传一个值 或多个     */    @SuppressWarnings("unchecked")    public static void del(String... key) {        if (key != null && key.length > 0) {            if (key.length == 1) {                redisTemplate.delete(key[0]);            } else {                redisTemplate.delete(CollectionUtils.arrayToList(key));            }        }    }    /**     * 获取缓存     *     * @param key   redis的key     * @param clazz value的class类型     * @param      * @return value的实际对象     */    public  static  T get(String key, Class clazz) {        Object obj = key == null ? null : redisTemplate.opsForValue().get(key);        if (!obj.getClass().isAssignableFrom(clazz)) {            throw new ClassCastException("类转化异常");        }        return (T) obj;    }    /**     * 获取泛型     *     * @param key 键     * @return 值     */    public static Object get(String key) {        return key == null ? null : redisTemplate.opsForValue().get(key);    }}

9. 创建RedisService及其实现类

RedisService类

package com.yyg.boot.service;/** * @Author 一一哥Sun * @Date Created in 2020/4/8 * @Description Description */public interface RedisService {    void setObj(String key, Object obj, long timeout);    Object getObj(String key);}

RedisServiceImpl类

package com.yyg.boot.service.impl;import com.yyg.boot.service.RedisService;import com.yyg.boot.util.RedisUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * @Author 一一哥Sun * @Date Created in 2020/4/8 * @Description Description */@Servicepublic class RedisServiceImpl implements RedisService {    @Autowired    private RedisUtil redisUtil;    @Override    public void setObj(String key, Object obj, long timeout) {        redisUtil.set(key,obj,timeout);    }    @Override    public Object getObj(String key) {        return redisUtil.get(key);    }}

10. 创建UserController测试接口

package com.yyg.boot.web;import com.yyg.boot.domain.User;import com.yyg.boot.repository.UserRepository;import com.yyg.boot.service.RedisService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;/** * @Author 一一哥Sun * @Date Created in 2020/3/31 * @Description Description */@RestController@RequestMapping("/user")public class UserController {    @Autowired    private UserRepository userRepository;    @Autowired    private RedisService redisService;    @GetMapping("/{id}")    public User findUserById(@PathVariable("id") Long id) {        User user = (User) redisService.getObj("user" + id);        if (user == null) {            user = userRepository.findById(id).get();            redisService.setObj("user" + id, user, 1000 * 60 * 2);            return user;        }        return user;    }}

12. 创建入口类

package com.yyg.boot;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * @Author 一一哥Sun * @Date Created in 2020/4/8 * @Description Description */@SpringBootApplicationpublic class RedisApplication {    public static void main(String[] args){        SpringApplication.run(RedisApplication.class,args);    }}

13. 完整目录结构

a2c383ef75f646f7b6a0a71872624635

14. 启动程序,进行测试

在我的数据库中,有这样的数据。

f1a1f46d51d3432e84bf5b98b64fec41

Redis数据库中默认没有缓存数据。

b0de82e328d141458d48a817686522ec

在浏览器中输入地址,进行查询。

915904b0875041559401d94c7ad98d12

此时再次去Redis DesktopManager中查看,会发现已经有了一条缓存的数据。

9332d9028b4d45e59c4687a87572157a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值