springBoot2.x整合redis

springBoot版本为2.0.4

一、基于代码的方式

方式一:基于redisTemplate

步骤:

1、引入redis的starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、springBoot配置文件中配置redis,例如:

############## redis的配置 ##############################
redis.host=localhost
redis.port=6379
redis.auth=123456
#连接和读写redis的超时时间
redis.timeOut=3000
#redis默认库号,0号库
redis.baseId=0

3、在需要用到的地方,自动注入RedisTemplate<Object,Object> redisTemplate(操作所有类型的数据) 或者StringRedisTemplate stringRedisTemplate(专门操作String类型),调用他的opsForxxx()方法,进而来调用redis的api即可。

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void test01() {
        Employee employee = (Employee)redisTemplate.opsForValue().get("employee::1");
        System.out.println(employee);

    }

小问题:

例如这样:用redisTemplate将一个对象放到redis中,redis中看着会乱码。

Employee employee = employeeMapper.getEmployeeById(1);
redisTemplate.opsForValue().set("emp1",employee);

原因是:由于redisTemplate默认的对象序列化方式为jdk自带的序列化方式,这样做便于redisTemplate进行反序列化,便于还原成原来的对象,其实不算是乱码。

若我们希望它能使以json字符串的方式存储到redis中,应该怎么做呢?

解决方法:

1、将对象转成json字符串存储。

2、改变RedisTemplate默认的序列化方式为json序列化。

方式1很简单先不说,这里我们说一下方式2:

根据上面分析的原因,只要我们改变一下redisTemplate的默认的对象序列化方式即可。springBoot允许我们这样做,在redis的自动配置类RedisAutoConfiguration中,往IOC容器中装配RedisTemplate时,有这样一个注解@ConditionalOnMissingBean,它的意思是当IOC容器中没有RedisTemplate时,才会使用自动装配的RedisTemplate,若有客户自定义的RedisTemplate,就是用自定义的RedisTemplate,所以我们只需要在配置类里面,配置一个RedisTemplate,然后通过@Bean注解注入到IOC容器中就行了。

配置一个RedisTemplate<Object,Object>覆盖默认的:

@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
    RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(connectionFactory);
    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    //设置成jackson序列化的方式,因为默认的jdk序列化对中文会乱码
    //或者每次都手动的序列化成json字符串和反序列化
    redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
    return redisTemplate;
}

这样往redis中存放数据是json的没问题,但是这样

Employee emp = (Employee)redisTemplate.opsForValue().get("emp1"); 
System.out.println(emp);

取数据时,就会报ClassCastException,原因就是配置的RedisTemplate<Object,Object>,value是Object类型,一个json数据反序列化成Object时,会用LinkedHashMap来存储,但是你LinkedHashMap强转成Employee类型,肯定会报错。解决办法也很简单,若要转换成json存储,就必须给每个要转换的类型配置一个独立的RedisTemplate<Object,beanType>才行。

/*----------操作Employee的RedisTemplate配置Jackson序列化方式*/
@Bean
public RedisTemplate<String,Employee> empRedisTemplate(RedisConnectionFactory connectionFactory){
    RedisTemplate<String,Employee> redisTemplate = new RedisTemplate<>();
    //设置连接工厂
    redisTemplate.setConnectionFactory(connectionFactory);
    Jackson2JsonRedisSerializer<Employee> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Employee.class);
    //设置成jackson序列化的方式,默认的jdk序列化对中文会乱码
    //或者每次都手动的序列化成json字符串和反序列化
    redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);

    return redisTemplate;
}

/*----------操作Department的RedisTemplate配置Jackson序列化方式*/
@Bean
public RedisTemplate<String,Department> deptRedisTemplate(RedisConnectionFactory connectionFactory){
    RedisTemplate<String,Department> redisTemplate = new RedisTemplate<>();
    //设置连接工厂
    redisTemplate.setConnectionFactory(connectionFactory);
    Jackson2JsonRedisSerializer<Department> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Department.class);
    //设置成jackson序列化的方式,默认的jdk序列化对中文会乱码
    //或者每次都手动的序列化成json字符串和反序列化
    redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);

    return redisTemplate;
}
...其他类型的RedisTemplate

 

然后注入刚才配置的RedisTemplate即可。

@Autowired
private RedisTemplate<String,Employee> redisTemplate;
@Autowired
private RedisTemplate<String,Department> deptRedisTemplate;

同理在用redis实现缓存注解时,也要为每个要存储的bean配置一个独立的xxxCacheManager,否则也会报类型转换异常。你也许会觉得这样太麻烦了,没办法,使用别人的框架还这么多要求,就得按照人家的方法来做。

总之:

1)若想省事就注入RedisTemplate<Object,Object>直接操作即可,什么都不用配置,但是要容许“视觉上的乱码”。

2)若想转换成json存储,就老老实实的给每个类型配置RedisTemplate吧。

或者使用现成的 redisUtils工具类,也很简单方便。

 

 

方式二:基于jedis

本人更倾向于使用jedis来操作redis,具体原因后面会说到。

具体的可以这么做:

利用redis的Java客户端jedis,自己封装一个redis操作工具类(基于jedis和jedisPool),里面单例模式一个jedisPool,获取和释放连接的方法,以及封装的api方法。

如果你要说自己封装比较麻烦,没有关系,java这么丰富的周边生态,还怕找不到一个小小的工具类吗?如果不嫌弃的话,本人也封装了一个基于jedis的工具类,基本涵盖了1V5中的所有api的操作方法,并配有详细的注释,目前也在持续更新中,欢迎来扰,一个超全的基于jedis的redis工具类:  https://blog.csdn.net/qq_40925189/article/details/109585667

关于该工具类的一些方法被调用:

RedisUtils.setex(token,Constant.Redis.tokenValidTime, jsonUser);
RedisUtils.hget(Constant.Redis.loginUserHashMap, String.valueOf(userId));
RedisUtils.hset(Constant.Redis.loginUserHashMap,String.valueOf(userId),token);
RedisUtils.del(token);
RedisUtils.hdel(Constant.Redis.loginUserHashMap,String.valueOf(id));

二、基于注解的方式

因为我们是使用spring的缓存注解来操作redis,而这些spring的缓存注解又是基于spring的缓存抽象,所以先知道了spring的缓存抽象是怎么样的,才能对这些缓存注解的使用得心应手。

1、spring中的缓存抽象

javaEE规范中的JSR-107规范,定义了在java中使用cache的一些规范,它定义了很多复杂的缓存组件概念和接口,并没有提供实现。由于这些复杂的概念和接口实现起来比较麻烦,所以spring从3.1开始,在JSR-107规范的基础上将缓存抽象成了简单的两部分org.springframework.cache.Cache和org.springframework.cache.CacheManager,来统一不同的缓存技术,并支持使用JCache(JSR-107)注解简化我们开发。

在spring中cache和cacheManager的定义和关系大致长这样:

spring缓存抽象中cache和cacheManager的关系

 

如图我们可以看到,缓存是建立在应用程序和数据库之间的一层。可以有多个CacheManager,由CacheManager管理着一个个的cache。

CacheManager:缓存管理器,管理各种缓存(Cache)组件,比如默认的ConcurrentMapCacheManager(使用的是SimpleCacheConfiguration配置类、cache组件为ConcurrentMapCache、将数据保存到一个ConcurrentHashMap中)、RedisCacheManager(使用的是RedisCacheConfiguration配置类,cache组件为RedisCache,数据存储到redis中)等;

Cache:缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache(默认)等。cache中包含许许多多的key-value对,被称为entry或element。缓存注解中的cacheNames属性,就表示某个cache的名字,必须是唯一的。key就表示cache里面某个entry的唯一标识key。

2、基于缓存注解操作redis

值得一提的是:spring缓存注解的实现是用的拦截器(interceptor),所以若是想统计每个controller方法的执行时间,需要使用更早加载的过滤器(filter)或者使用@order(1)注解指定多个拦截器的加载顺序。

步骤:很简单

1、引入spring缓存抽象的依赖和redis的starter的依赖

<!--引入spring的缓存抽象-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!--redis的starter-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、在启动类或任何配置类上添加@EnableCaching(开启基于注解的缓存)

3、针对每个bean类型,配置对应的cacheManager。(为什么要给每一个bean类型配置对应的cacheManager,在下面的小问题中会说到。)

4、在controller或serviceImpl的方法上加上缓存注解即可(将方法的返回值放入redis中)。

一个简单的小栗子:

@Cacheable(cacheNames = "employee",key = "#id")
@GetMapping("/emp/{id}")
public Employee getEmpById(@PathVariable("id") Integer id){
    Employee employee = employeeMapper.getEmployeeById(id);
    return employee;
}

//@CachePut会先到缓存中查询一下,有就更新,没有就添加。
@CachePut(cacheNames = "employee",key = "#employee.id")
@GetMapping("/addEmp")
public Employee addEmp(Employee employee){
    employeeMapper.addEmployee(employee);
    return employee;
}

@CachePut(cacheNames = "employee",key = "#employee.id")
public Employee updateEmployee(Employee employee){
    employeeMapper.updateEmployee(employee);
    return employee;
}

@CacheEvict(cacheNames = "employee",key = "#id")
//@CacheEvict(cacheNames = "employee",allEntries = true)
@GetMapping("/deleteEmp")
public String deleteEmployee(Integer id){
    System.out.println("删除id为"+id+"的员工。");
    return "删除成功!";
}
//@Caching组合多个注解,如此时添加员工后,往缓存中以key为id、username、
//email,value为employee放了三个k-v对,此时即使没有根据email查询的方法,
//因为缓存中有,也可以从缓存中取出。如果缓存中没有去查数据库,因为是根据Integer
//类型的数据去查询的,所以此时数据会封装不上
@Caching(  
    put = {  
        @CachePut(value = "user", key = "#user.id"),  
        @CachePut(value = "user", key = "#user.username"),  
        @CachePut(value = "user", key = "#user.email")  
    }  
)  
public Employee addEmp(Employee employee){
    return employeeMapper.addEmployee(employee);
}

为什么导入redis的starter,就能让缓存注解的实现变成redis呢?

切换的原理:

默认情况下,spring对缓存注解的缓存实现是SimpleCache,即cacheManager是ConcurrentMapCacheManager,cache组件为ConcurrentMapCache,我们只要将注解的缓存实现变成redis就行了,即cacheManager为RedisCacheManager,cache组件为RedisCache,怎么做呢?

org.springframework.boot.autoconfigure.cache包下有个CacheConfigurations类,定义了各种cacheConfiguration的加载顺序,每个cacheConfiguration上面都有一个@ConditionalOnMissingBean({CacheManager.class})注解(即保证了先加载的cacheConfiguration若往IOC容器中装配一个CacheManager类,其他的cacheConfiguration都不会生效),而RedisCacheConfiguration(redis支持缓存注解时的自动配置类)的加载顺序在默认的SimpleCacheConfiguration之前,所以说若能保证RedisCacheConfiguration生效,就能将对缓存注解的支持变成redis。RedisCacheConfiguration类上面有一个注解@ConditionalOnClass({RedisConnectionFactory.class}),即若IOC容器中有RedisConnectionFactory这个类,就能使RedisCacheConfiguration生效,当这个RedisCacheConfiguration生效后会自动往IOC容器装配一个RedisCacheManager,RedisCacheManager又会自动生成RedisCache,所以只要IOC容器中有RedisConnectionFactory这个类,就能成功的将缓存注解的支持变成redis,而恰好RedisConnectionFactory这个类就在我们导入的spring-boot-starter-data-redis里面

结论:只要导入了spring-boot-starter-data-redis这个依赖,就能让redis来做spring缓存抽象的支持,就能通过注解实现缓存数据到redis。

小问题

同上面使用redisTemplate时一样,RedisCacheConfiguration默认的对象序列化方式不是json格式的而是二进制的,也是不影响反序列化。但若想用json进行序列化,需要进行如下配置:

springBoot2.x

方式1(推荐)、为每一个要缓存的对象所属的类,都设置一个缓存管理器,并指定为json序列化。(这么做的原因,猜想可能是:利用json工具手动序列化和反序列化时,我们知道要反序列化后的对象的类型,但现在基于注解自动反序列化时,可能不确定要反序列化的对象类型,所以需要每个类型都指定一下,否则就会报类型转换异常)。

方式2、或者value的序列化方式用这个 new GenericJackson2JsonRedisSerializer(),这样只写一个xxxCacheManager就行,但是json数据中会带着"@class": "com.test.springboot.bean.Employee"这个信息,只能用原来注解序列化的方式反序列化,不能用工具类手动的反序列化,通用性不强。

关于方式2可以这样做:

@Bean
    public RedisCacheManager generalCacheManager(RedisConnectionFactory connectionFactory){
        Jackson2JsonRedisSerializer<String> stringSerializer = new Jackson2JsonRedisSerializer<>(String.class);   
        //配置redis
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofDays(1))//设置缓存有效期为一天
                .computePrefixWith(cacheName -> cacheName.concat(":"))//设置缓存key的前缀为cacheName:key的形式
                .disableCachingNullValues();
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
        return redisCacheManager;
    }

关于方式1这样做:

/**
     * 配置Employee类型的缓存管理器
     * */
    @Primary //当有多个缓存管理器时,必须要指定一个是主要的
    @Bean
    public RedisCacheManager empCacheManager(RedisConnectionFactory connectionFactory){
        Jackson2JsonRedisSerializer<String> stringSerializer = new Jackson2JsonRedisSerializer<>(String.class);
        Jackson2JsonRedisSerializer<Employee> employeeSerializer = new Jackson2JsonRedisSerializer<>(Employee.class);
        //RedisCacheConfiguration配置序列化方式
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(employeeSerializer))
                .prefixKeysWith("aaa") //缓存key的前缀为aaa1的形式
                .disableCachingNullValues();
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();

        return redisCacheManager;
    }

/**
     * 配置Department类型的缓存管理器
     */
    //@Bean
    public RedisCacheManager deptCacheManager(RedisConnectionFactory connectionFactory){
        Jackson2JsonRedisSerializer<String> stringSerializer = new Jackson2JsonRedisSerializer<>(String.class);
        Jackson2JsonRedisSerializer<Department> deptSerializer = new Jackson2JsonRedisSerializer<>(Department.class);
        //默认1
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(deptSerializer))
                .entryTtl(Duration.ofDays(1))//设置缓存有效期为一天
                .computePrefixWith(cacheName -> cacheName.concat(":"))//设置缓存key的前缀为cacheName:key的形式
                .disableCachingNullValues();
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
        return redisCacheManager;
    }
    //key的生成策略,可以在@Cacheable注解中使用该策略
    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator(){

            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString()+"]";
            }
        };

然后就可以在缓存注解中使用keyGenerator和cacheManager指定要使用哪个key的生成规则、和定义的cacheManager了。

在springBoot1.x时针对方式1可以这样做:

RedisCacheManager的构建是基于RedisTemplate,而RedisTemplate的默认序列化方式也是基于JDK的,看着像乱码,所以我们要改成使用json序列化的方式。

@Bean
    public RedisTemplate<Object, Employee> empRedisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(ser);
        return template;
    }

    @Primary  //将某个缓存管理器作为默认的
    @Bean
    public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
        RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
        //使用前缀,默认会将CacheName作为key的前缀
        cacheManager.setUsePrefix(true);
        //其他配置 
        return cacheManager;
    }

关于缓存注解参数的详解:

@Cacheable、@CachePut、@CacheEvict、的常用参数

1、cacheNames/value:指定使用一个或多个cache,必须指定一个。

        如:cacheNames="emp" / value={"emp","cache2","cache3"}

2、key:某个cache中某个缓存的唯一标识。当参数为一个时,默认为这个参数名 当参数为多个时,key为参数的组合。指定时需要按照spEL表达式的方式指定。

如:key="#id" / key="p0"

3、keyGenerator:指定缓存key的生成器。

4、cacheManager:指定缓存对应的缓存管理器,否则使用@Primary标注的缓存管理器。

3、condition:条件判断,根据condition指定的条件判断为true时才会被缓存/清除。

如:@Cacheable(cacheNames="emp",key="id",condition="#username.length()>3")

4、allEntries(@CacheEvict的属性):默认为false,为true时,删除指定cache中的全部key-value对,在方法执行完毕后删除缓存。

5、beforeInvocation(@CacheEvict的属性):默认为false,为true时则在方法执行之前就清除缓存。

6、unless(@CachePut/@Cacheable的属性):根据条件判断,和confition相反,为false时才进行缓存。

cache中可用的spEL表达式

1、argumentname:方法参数的名字,可以直接 #参数名 ,也可以使用 #p0 或 #a0 的形式,0代表参数的索引;如:#id、 #a0 、 #p0

2、args:被调用方法的参数列表。如: key="#root.args[0]" 代表使用第一个参数。

3、result:代表方法返回的结果(仅当方法一定会被执行时) 如: key="#result.id"

 

虽然本文完成了用缓存注解来操作redis,但不推荐用缓存注解来做,还是推荐编码的方式来做,因为比较可控。关于缓存注解,可以使用一些本地缓存框架来实现,从而可以做一个简单的二级缓存功能。

本文springBoot整合redis到此就算结束了,有什么疑问和建议可以在评论区提出,欢迎指正。

 

上一篇:7、redis的主从复制

下一篇:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值