Spring Boot集成Spring Cache 和 Redis

Posted 仍是少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot集成Spring Cache 和 Redis相关的知识,希望对你有一定的参考价值。

在Spring Boot中添加spring-boot-starter-data-redis依赖:

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

在application.properties中指定redis服务器IP、端口和密码、连接数等:

# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口 使用默认端口6379可以省略配置
#spring.redis.port=6379
# Redis服务器连接密码(默认为空)
#spring.redis.password=
# 连接池最大连接数(如果配置<=0,则没有限制 )
spring.redis.jedis.pool.max-active=8

使用StringRedisTemplate 和RedisTemplate

StringRedisTemplate是Spring Boot内置的操作Redis的API实现类,另外还有一个API实现类是RedisTemplate。StringRedisTemplate的API假定所有的数据类型化都是字符类型,即key和value都是字符串类型,对于常见额SpringBoot应用系统,使用字符串操作也已经足够了,而且能方便的通过客户端管理工具管理。StringRedisTemplate继承RedisTemplate,与RedisTemplate不同的是重新设置了序列化策略,使用StringRedisSerialier类来序列化key-value,包括List、Hash、Set等数据结构。

 1 @RunWith(SpringRunner.class)
 2 @SpringBootTest
 3 public class SpringbootCacheApplicationTests {
 4 
 5     @Autowired
 6     StringRedisTemplate stringRedisTemplate; //操作 k-v 字符串
 7 
 8     @Autowired
 9    RedisTemplate redisTemplate;  //k- v 都是对象
10 
11     /**
12      * redis 常见
13      * String(字符串) List(列表) Set(集合) Hash(散列) ZSet(有序集合)
14      */
15 
16     @Test
17     public void test1() {
18         stringRedisTemplate.opsForValue().append("StringKey", "字符串数值");
19         String value = stringRedisTemplate.opsForValue().get("StringKey");
20         System.out.println(value);
21     }
22   @Test
23   public void test2() {
24 
25     Product product =  productMapper.getProductById(4L);
26     redisTemplate.opsForValue().set("produxtid4",product);
27 
28 }
29 }

spring-boot-autoconfigure-2.0.4.RELEASE.jar包中RedisAutoConfiguration.java已经自动声明了两个redis操作bean:

RedisAutoConfiguration.java实现代码:

因此我们只要在使用的地方注入即可:

@Autowired
StringRedisTemplate stringRedisTemplate; //操作 k-v 字符串
@Autowired
RedisTemplate redisTemplate;  //k- v 都是对象

StringRedisTemplate 提供opsForValue用来操作key-value,如上面的示例,另外还提供了一系列opsForHash()、opsForList()、opsForSet()、opsForZSet()等方法用来操作不同结构类型的数据。

运行上面的测试方法,查看redis客户端:

 

可以发现,出现了一些无法识别的字符,查看RedisTemplate源码可这个是由于默认使用了JDK的序列化机制,而StringRedisTemplate没有出乱码是因为它修改了序列化器

StringRedisTemplate实现:

 1 public class StringRedisTemplate extends RedisTemplate<String, String> {
 2 
 3    /**
 4     * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
 5     * and {@link #afterPropertiesSet()} still need to be called.
 6     */
 7    public StringRedisTemplate() {
 8       RedisSerializer<String> stringSerializer = new StringRedisSerializer();
 9       setKeySerializer(stringSerializer);
10       setValueSerializer(stringSerializer);
11       setHashKeySerializer(stringSerializer);
12       setHashValueSerializer(stringSerializer);
13    }
14 
15    /**
16     * Constructs a new <code>StringRedisTemplate</code> instance ready to be used.
17     *
18     * @param connectionFactory connection factory for creating new connections
19     */
20    public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
21       this();
22       setConnectionFactory(connectionFactory);
23       afterPropertiesSet();
24    }
25 
26    protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
27       return new DefaultStringRedisConnection(connection);
28    }
29 }
View Code

RedisTemplate实现,截取默认序列化器相关源码:

 1 @Override
 2 public void afterPropertiesSet() {
 3 
 4    super.afterPropertiesSet();
 5 
 6    boolean defaultUsed = false;
 7 
 8    if (defaultSerializer == null) {
 9      //默认序列化器使用JdkSerializationRedisSerializer
10       defaultSerializer = new JdkSerializationRedisSerializer(
11             classLoader != null ? classLoader : this.getClass().getClassLoader());
12    }
13 
14    if (enableDefaultSerializer) {
15 
16       if (keySerializer == null) {
17          keySerializer = defaultSerializer;
18          defaultUsed = true;
19       }
20       if (valueSerializer == null) {
21          valueSerializer = defaultSerializer;
22          defaultUsed = true;
23       }
24       if (hashKeySerializer == null) {
25          hashKeySerializer = defaultSerializer;
26          defaultUsed = true;
27       }
28       if (hashValueSerializer == null) {
29          hashValueSerializer = defaultSerializer;
30          defaultUsed = true;
31       }
32    }
33 
34    if (enableDefaultSerializer && defaultUsed) {
35       Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
36    }
37 
38    if (scriptExecutor == null) {
39       this.scriptExecutor = new DefaultScriptExecutor<>(this);
40    }
41 
42    initialized = true;
43 }
View Code

既然RedisTemplate的默认序列化器不是很方便在redis管理工具中查看,我们可以自己定义一个RedisTemplate实例,修改默认的序列化器。

实现方式如下,定义一个配置类,重新注入一个RedisTemplate操作bean:

 1 @Configuration
 2 public class MyRedisConfig {
 3 
 4    @Bean(name = "redisTemplate")
 5      public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
 6 
 7         RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
 8      //参照StringRedisTemplate内部实现指定序列化器
 9         redisTemplate.setConnectionFactory(redisConnectionFactory);
10         redisTemplate.setKeySerializer(keySerializer());
11         redisTemplate.setHashKeySerializer(keySerializer());
12         redisTemplate.setValueSerializer(valueSerializer());
13         redisTemplate.setHashValueSerializer(valueSerializer());
14 
16         return redisTemplate;
17     }
18 
19     private RedisSerializer<String> keySerializer() {
20         return new StringRedisSerializer();
21     }
22   //使用Jackson序列化器
23     private RedisSerializer<Object> valueSerializer() {
24         return new GenericJackson2JsonRedisSerializer();
25     }
26 }

 重新运行上面的测试代码,可以发现redis客户端中已经可以正常的显示json格式数据了。

SpringBoot集成redis + spring cache

Spring Cache集成redis的运行原理:

Spring缓存抽象模块通过CacheManager来创建、管理实际缓存组件,当SpringBoot应用程序引入spring-boot-starter-data-redi依赖后吗,容器中将注册的是CacheManager实例RedisCacheManager对象,RedisCacheManager来负责创建RedisCache作为缓存管理组件,由RedisCache操作redis服务器实现缓存数据操作。实际测试发现默认注入的RedisCacheManager操作缓存用的是RedisTemplate<Object, Object>,因此我们需要自定义cacheManager,替换掉默认的序列化器。

实现代码:

添加mybatis和redis依赖:

<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>

添加mapper映射:

 1 @Mapper
 2 public interface ProductMapper {
 3     @Select("select * from tb_product where product_id=#{id}")
 4     Product getProductById(Long id);
 5 
 6     @Update("update tb_product set product_name=#{productName},product_desc=#{productDesc} WHERE product_id=#{productId}")
 7     int updateProduct(Product product);
 8 
 9     @Delete("delete from tb_product where product_id=#{id}")
10     void deleteProductById(Long id);
11 
12     @Select("select * from tb_product where product_name=#{productName}")
13     Product getProductByName(String productName);
14 }

Service:

 

 1 package com.sl.cache.service;
 2 import com.sl.cache.entity.Product;
 3 import com.sl.cache.mapper.ProductMapper;
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import org.springframework.cache.annotation.CacheConfig;
 6 import org.springframework.cache.annotation.CacheEvict;
 7 import org.springframework.cache.annotation.CachePut;
 8 import org.springframework.cache.annotation.Cacheable;
 9 import org.springframework.cache.annotation.Caching;
10 import org.springframework.stereotype.Service;
11 
12 @Service
13 @CacheConfig(cacheNames = "product")
14 public class ProductService {
15     @Autowired
16     private ProductMapper productMapper;
17 
18     @Cacheable(cacheNames = "product1",key = "#root.methodName+\'[\'+#id+\']\'")
19     //@Cacheable(cacheNames = {"product1","product2"})// 默认key为参数,多个参数SimpleKey [arg1,arg2]
20     //@Cacheable(cacheNames = "product",key = "#root.methodName+\'[\'+#id+\']\'")
21     //@Cacheable(cacheNames = "product",keyGenerator = "myKeyGenerator")
22     //@Cacheable(cacheNames = "product",key = "#root.methodName+\'[\'+#id+\']\'",condition="#a0>10",unless = "#a0==11") //或者condition="#id>10")
23     public Product getProductById(Long id){
24        Product product =productMapper.getProductById(id);
25        System.out.println(product);
26        return product;
27     }
28 
29     @CachePut(value="product",key = "#result.productId",condition = "#result!=null")
30     public  Product updateProduct(Product product){
31         int count = productMapper.updateProduct(product);
32         System.out.println("影响行数:"+count);
33         if(count>0){
34             return product;
35         }else{
36             return null;
37         }
38     }
39 
40     //@CacheEvict(value="product",key="#id")
41     //@CacheEvict(value="product",allEntries = true) //清楚所有缓存
42     @CacheEvict(value="product",allEntries = true,beforeInvocation = true) //清楚所有缓存
43     public boolean deleteProductById(Long id) {
44         productMapper.deleteProductById(id);
45         return true;
46     }
47 
48     //含有CachePut注解,所以执行这个方法时一定会查询数据库,及时有cacheable注解
49     @Caching(
50             cacheable = {@Cacheable(value="product",key="#productName")},
51             put = {
52                     @CachePut(value="product",key="#result.productId"),
53                     @CachePut(value="product",key="#result.productName")
54             }
55     )
56     public  Product getProductByName(String productName){
57 
58         Product product =productMapper.getProductByName(productName);
59 
60          return product;
61     }
62 }

Controller:

package com.sl.cache.controller;
import com.sl.cache.entity.Product;
import com.sl.cache.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("id") Long id) {

        Product product = productService.getProductById(id);
        return product;
    }

    //prooduct?productid=1&productName= &
    @GetMapping("/product")
    public Product updateProduct(Product product) {
        productService.updateProduct(product);
        return product;
    }

    @GetMapping("/delproduct")
    public String delProduct(@RequestParam(value="id") Long id) {

        productService.deleteProductById(id);
        return "ok";
    }

    @GetMapping("/product/name/{productName}")
    public Product getEmpByLastName(@PathVariable("productName") String productName){
        return productService.getProductByName(productName);
    }
}

自定义cacheManager实现:

 1 package com.sl.cache.config;
 2 import com.sl.cache.entity.Product;
 3 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 4 import org.springframework.cache.CacheManager;
 5 import org.springframework.cache.config.CacheManagementConfigUtils;
 6 import org.springframework.context.annotation.Bean;
 7 import org.springframework.context.annotation.Configuration;
 8 import org.springframework.context.annotation.Primary;
 9 import org.springframework.data.redis.cache.RedisCacheConfiguration;
10 import org.springframework.data.redis.cache.RedisCacheManager;
11 import org.springframework.data.redis.cache.RedisCacheWriter;
12 import org.springframework.data.redis.connection.RedisConnectionFactory;
13 import org.springframework.data.redis.core.RedisTemplate;
14 import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
15 import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
16 import org.springframework.data.redis.serializer.RedisSerializationContext;
17 import org.springframework.data.redis.serializer.RedisSerializer;
18 import org.springframework.data.redis.serializer.StringRedisSerializer;
19 
20 import java.net.UnknownHostException;
21 import java.time.Duration;
22 
23 @Configuration
24 public class MyRedisConfig {
25 
26     @Bean(name = "redisTemplate")
27     public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
28 
29         RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
30 
31         redisTemplate.setConnectionFactory(redisConnectionFactory);
32         redisTemplate.setKeySerializer(keySerializer());
33         redisTemplate.setHashKeySerializer(keySerializer());
34         redisTemplate.setValueSerializer(valueSerializer());
35         redisTemplate.setHashValueSerializer(valueSerializer());
36         return redisTemplate;
37     }
38 
39     @Primary
40     @Bean
41     public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
42         //缓存配置对象
43         RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
44 
45         redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) //设置缓存的默认超时时间:30分钟
46                 .disableCachingNullValues()             //如果是空值,不缓存
47                 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))         //设置key序列化器
48                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer((valueSerializer())));  //设置value序列化器
49 
50         return RedisCacheManager
51                 .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
52                 .cacheDefaults(redisCacheConfiguration).build();
53     }
54     private RedisSerializer<String> keySerializer() {
55         return new StringRedisSerializer();
56     }
57 
58     private RedisSerializer<Object> valueSerializer() {
59         return new GenericJackson2JsonRedisSerializer();
60     }
61 }

启用缓存,添加mybatis Mapper映射扫描:

 1 @MapperScan("com.sl.cache.mapper")
 2 @SpringBootApplication
 3 @EnableCaching
 4 public class SpringbootCacheApplication {
 5 
 6     public static void main(String[] args) {
 7         SpringApplication.run(SpringbootCacheApplication.class, args);
 8 
 9     }
10 }

 

以上是关于Spring Boot集成Spring Cache 和 Redis的主要内容,如果未能解决你的问题,请参考以下文章

spring boot redis 缓存(cache)集成

Spring Boot (24) 使用Spring Cache集成Redis

Spring Boot之集成Redis:Spring Cache + Redis

spring boot:使用spring cache+caffeine做进程内缓存(本地缓存)(spring boot 2.3.1)

WebFlux 集成 Redis 实现缓存 | Spring Boot 2

史上最全的Spring Boot Cache使用与整合