• SpringBoot(三)缓存


    1 默认缓存管理

    Spring框架支持透明地向应用程序添加缓存对缓存进行管理,其管理缓存地核心是将缓存应用于操作数据地方法,从而减少操作数据地执行次数,同时不会对程序本身造成任何干扰。
    Spring Boot继承了Spring框架的缓存管理功能,通过使用@EnableCaching注解开启基于注解的缓存支持,Spring Boot就可以启动缓存管理的自动化配置。

    1.1 环境搭建

    1.1.1 准备数据

    创建一个lxsh_test的数据库,该数据库有两个表t_article和t_comment

    1.1.2 创建项目,功能编写

    (1) 在Dependencies依赖选择项中添加SQL模块中的JPA依赖、MySQL依赖和Web模块中的Web依赖
    (2) 编写数据库表对应的实体类,并使用JPA相关注解配置映射关系

    import javax.persistence.*;
    @Entity(name = "t_comment")
    public class Comment {
    	@Id//表映射对应的主键ID
    	@GenerateValue(strategy = GenerationType.IDENTITY)//设置主键策略
    	private Integer id;
    	private String content;
    	private String author;
    	@Column(name = "a_id")//指定映射的表字段名
    	private Integer aId;
    	//省略getXX()和setXX()方法
    	//省略toString()方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (3) 编写数据库操作的Repository接口文件

    public interface CommentRepository extends JpaRepositroy<Comment,Integer> {
    	//根据评论id修改评论作者author
    	@Transactional
    	@Modifying
    	@Query("update t_comment c set c.author=?1 where c.id=?2",nativeQuery = true)
    	public int updateComment(String author,Integer id);
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (4) 编写Service层

    @Service
    public class CommentService {
    	@Autowired
    	private CommentRepository commentRepository;
    
    	public Comment findCommentById(Integer id){
    		Optional<Comment> comment = commentRepository.findById(id);
    		if(comment.isPresent()){
    			Comment model = comment.get();
    			return model;
    		}
    		return null;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (5) 编写Controller层

    @RestController
    public class CommentController {
    	@Autowired
    	private CommentService commentService;
    
    	@GetMapping("/findCommentById")
    	public Comment findCommentById(Integer id){
    		Comment comment = commentService.findCommentById(id);
    		return comment;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (6) 编写配置文件
    在项目全局配置文件application.yml中编写对应的数据库连接配置

    spring:
      #MySQL数据库连接配置
      datasource:
        url: jdbc:mysql://localhost:3306/lxsh_test?serverTimezone=UTC
        username: root
        password: root
      jpa:
        show-sql: true # 显示使用JPA进行数据查询的sql语句
      http:
        encoding:
          force-response: true # 解决乱码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (7) 运行项目进行测试

    1.2 默认缓存体验

    在前面搭建的Web应用基础上,开启Spring Boot默认支持的缓存,体验Spring Boot默认缓存的使用效果
    (1) 使用@EnableCaching注解开启基于注解的缓存支持

    @EnableCaching //开启Spring Boot基于注解的缓存管理支持
    @SpringBootApplication
    public class TestApplication {
    	public static void main(String[] args){
    		SpringApplication.run(TestApplication.class,args);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2) 使用@Cacheable注解对数据操作方法进行缓存管理。将@Cacheable注解标注在Service类的查询方法上,对查询结果进行缓存

    //根据评论ID查询评论信息
    //key:默认在只有一个参数的情况下,key值默认就是方法参数值;如果没有参数或者多个参数的情况,simpleKeyGenerate生成key
    //value:缓存结果
    @Cacheable(cacheNames= "comment")
    public Comment findCommentById(Integer id){
    		Optional<Comment> comment = commentRepository.findById(id);
    		if(comment.isPresent()){
    			Comment model = comment.get();
    			return model;
    		}
    		return null;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上述代码中,在CommentService类中的findCommentById(Integer id)方法上添加了查询缓存注解@Cacheable,该注解的作用是将查询结果Comment存放在Spring Boot默认缓存中名为comment的名称空间(namespace)中,对应缓存唯一标识(即缓存数据对应的主键k)默认为方法参数id的值
    (3) 访问结果分析
    在诸多的缓存自动配置类中,SpringBoot默认装配的是SimpleCacheConfiguration,它使用的CacheManager是ConcurrentMapCacheManager,使用CurrentMap当底层数据结构,按照Cache的名字查询出Cache,每一个Cache中存在多个k-v键值对,缓存值

    1.3 缓存注解介绍

    上述代码中使用@EnableCaching、@Cacheable注解实现了Spring Boot默认的基于注解的缓存管理,除此之外,还有更多的缓存注解以及注解属性可以配置优化缓存管理

    1.3.1 @EnableCaching注解

    @EnableCaching是由Spring框架提供的,SpringBoot框架对该注解进行了继承,该注解需要配置在类上(通常是在项目启动类上),用于开启基于注解的缓存支持

    1.3.2 @Cacheable注解

    @Cacheable注解也是由Spring框架提供的,可以作用于类或方法(通常用在数据查询方法上),用于对方法结果进行缓存存储。注解的执行顺序是,先进行缓存查询,如果为空则进行方法查询,并将结果进行缓存;如果缓存中有数据,不进行方法查询,而是直接使用缓存数据
    @Cacheable注解提供了多个属性,用于对缓存存储进行相关配置

    属性名说明
    value/cacheNames指定缓存空间的名称,必配属性。这两个属性二选一使用
    key指定缓存数据的key,默认使用方法参数值,可以使用SpEL表达式
    keyGenerator指定缓存数据的key的生成器,与key属性二选一使用
    cacheManager指定缓存管理器
    cacheResolver指定缓存解析器,与cacheManager属性二选一使用
    condition指定在符合某条件下,进行数据缓存
    unless指定在符合某条件下,不进行数据缓存
    sync指定是否使用异步缓存。默认false

    执行流程&时机
    方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名称获取,(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建;在Cache中查询缓存的内容,使用一个key,默认就是方法的参数,如果多个参数或者没有参数,是按照某种策略生成的,默认是使用KeyGenerator生成的,使用SimpleKeyGenerator生成key,SimpleKeyGenerator生成key的默认策略:

    参数个数key
    没有参数new SimpleKey()
    有一个参数参数值
    多个参数new SimpleKey(params)

    常用的SPEL表达式

    描述示例
    当前被调用的方法名#root.methodName
    当前被调用的方法#root.method
    当前被调用的目标对象#root.target
    当前被调用的目标对象类#root.targetClass
    当前被调用的方法的参数列表#root.args[0]表示第一参数,#root.args[1]表示第二参数,…以此类推
    根据参数名字取出值#参数名,也可以使用 #p0 #a0 0是参数的下标索引
    当前方法的返回值#result

    1.3.3 @CachePut注解

    目标方法执行之后生效,@CachePut被使用于修改操作比较多,哪怕缓存中已经存在目标值了,但是这个注解保证这个方法依然会执行,执行之后的结果被保存在缓存中
    @CachePut注解也提供了多个属性,这些属性与@Cacheable注解的属性完全相同。
    更新操作,前端会把id+实体传递到后端使用,我们就直接执行方法的返回值重新存进缓存时的key=“#id”,如果前端只给了实体,我们就使用key=“#实体.id"获取key,同时他的执行时机时目标方法结束后执行,所以可以使用 key=”#result.id",拿出返回值的id。

    1.3.4 @CacheEvict注解

    @CacheEvict注解是由Spring框架提供的,可以用于作用类或方法(通常用在数据删除方法上),该注解的作用时删除缓存数据。@CacheEvict注解的默认执行顺序是:先进行方法调用,然后将缓存进行清除。

    2 整合Redis缓存

    2.1 SpringBoot支持的缓存组件

    在SpringBoot中,数据的缓存管理依赖于Spring框架中cache相关的org.springframework.cache.Cache和org.springframework.cache.CacheManager缓存管理器接口。
    如果程序中没有定义类型为CacheManager的Bean组件或者是名为cacheResolver的CacheResolver缓存解析器,SpringBoot将尝试选择并启动以下缓存组件(按照指定顺序):

    1. Generic
    2. JCache(JSR-107)(EhCache3、Hazelcast、Infinispan等)
    3. EhCache 2.x
    4. Hazelcast
    5. Infinispan
    6. Couchbase
    7. Redis
    8. Caffeine
    9. Simple

    上述按照SpringBoot缓存组件的加载顺序,列举了支持的9中缓存组件,在项目中 添加某个缓存管理组件(例如Redis)后,SpringBoot项目会选择并启用对应的缓存管理器。如果项目中同时添加了多个缓存组件,且没有指定缓存管理器或者缓存解析器(CacheManager或者CacheResolver),那么SpringBoot会按照上述顺序在添加的多个缓存中优先启用指定的缓存组件进行缓存管理。
    上述讲解的Spring Boot默认缓存管理中,没有添加任何缓存管理组件能实现缓存管理。这是因为开启缓存管理后,Spring Boot会按照上述列表顺序查找有效的缓存组件进行缓存管理,如果没有任何缓存组件,会默认使用最后一个Simple缓存组件进行管理。Simple缓存组件时Spring Boot默认的缓存管理组件,它默认使用了内存中的ConcurrentMap进行缓存存储,所以在没有添加任何第三方缓存组件的情况下,可以实现内存中的缓存管理,但是我们不推荐使用缓存管理方式。

    2.2 基于注解的Redis缓存实现

    在SpringBoot默认缓存管理的基础上引入Redis缓存组件,使用基于注解的方式讲解SpringBoot整合Redis缓存的具体实现
    (1)添加Spring Data Redis依赖启动器。在pom.xml文件中添加Spring Data Redis依赖启动器

    <dependency>
    	<groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    当我们添加进redis相关的启动器之后,SpringBoot会使用RedisCacheConfiguration当做生效的自动配置类进行缓存相关的自动装配,容器中使用的缓存管理器是RedisCacheManager,这个缓存管理器创建的Cache为RedisCache,进而操控Redis进行数据的缓存
    (2)Redis服务连接配置:

    spring:
      redis:
        host: 127.0.0.1 #Redis服务地址
        port: 6379 #Redis服务端口
        password: #Redis服务连接密码(默认为空)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (3)对CommentService类中的方法进行修改使用@Cacheable、@CachePut、@CacheEvict三个注解定制缓存管理,分别进行缓存存储、缓存更新和缓存删除的演示

    @Service
    public class CommentService {
    	@Autowired
    	private CommentRepository commentRepository;
    
    	//查询方法
    	@Cacheable(cacheNames="comment",unless="#result==null")
    	public Comment findCommentById(Integer id){
    		Optional<Comment> comment = commentRepository.findById(id);
    		if(comment.isPresent()){
    			Comment model = comment.get();
    			return model;
    		}
    		return null;
    	}
    
    	//更新方法
    	@CachePut(cacheNames = "comment", key="#result.id")
    	public Comment updateComment(Comment comment){
    		commentRepository.updateComment(comment.getAuthor(),comment.getId());
    		return comment;
    	}
    	
    	//删除方法
    	@CacheEvict(cacheNames = "comment")
    	public void deleteComment(Integer id){
    		commentRepository.deleteById(id);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    以上使用@Cacheable、@CachePut、@CacheEvict注解在数据查询、更新和删除方法上进行了缓存管理。
    其中,查询缓存@Cacheable注解中没有标记key值,将会使用默认参数值id作为key进行数据存储,在进行缓存更新时必须使用同样的key,同时在查询缓存@Cacheable注解中,定义了unless="#result==null"表示查询结果为空不进行缓存
    (4)基于注解的Redis查询缓存测试在这里插入图片描述
    可以看出,查询用户评论信息Comment时执行了相应的SQL语句,但是在进行缓存存储时出现了IllegalArgumentException非法参数异常,提示信息要求对应Comment实体类必须实现序列化(“DefaultSerializer requires a Serializable payload but received an object of type”)
    (5)将缓存对象实现序列化

    @Entity(name = "t_comment")
    public class Comment implements Serializable{
    	...
    }
    
    • 1
    • 2
    • 3
    • 4

    (6)再次启动测试
    访问“http://localhost:8443/findCommentById?id=1”查询id为1的用户评论信息,并重复刷新浏览器进行查询同一条数据信息,发现控制台并没由sql语句输出
    (7)基于注解的Redis缓存更新测试
    执行updateComment()方法更新id为1的数据时执行了一条更新sql语句,后续调用findById()方法查询id为1的用户评论信息时并没有执行查询语句,且浏览器返回了更新后结果,表面@CachePut缓存更新配置成功
    (8)基于注解的Redis缓存删除测试
    执行deleteComment()方法删除id为1的数据后查询结果为空,之气存储在Redis数据库的comment相关数据也被删除,表明@CacheEvict缓存删除成功

    通过上面的案例可以看出,使用基于注解的Redis缓存实现只需要添加Redis依赖并使用几个注解可以实现对数据的缓存管理。另外,还可以咋SpringBoot全局配置文件中配置Redis有效期,示例代码如下:

    spring:
      cache:
        redis:
          time-to-live: 60000 #对基于注解的redis缓存数据统一设置有效期一分钟,单位毫秒
      redis:
        host: 127.0.0.1 #Redis服务地址
        port: 6379 #Redis服务端口
        password: #Redis服务连接密码(默认为空)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.3 基于API的Redis缓存实现

    在SpringBoot整合Redis缓存实现中,除了基于注解形式的Redis缓存实现外,还有一种开发中常用的方式——基于API的Redis缓存实现。这种基于API的缓存实现,需要在某种业务需求下通过Redis提供的API调用相关方法实现数据缓存管理;同时,这种方法还可以手动管理缓存的有效期。
    下面,通过Redis API的方式讲解SpringBoot整合Redis缓存的具体实现
    使用Redis API进行业务数据缓存管理。在com.lxsh.service包下编写一个进行业务处理的类ApiCommentService

    @Service
    public class ApiCommentService {
    	@Autowired
    	private CommentRepository commentRepository;
    	
    	@Autowired
    	private RedisTemplate redisTemplate;
    
    	//查询方法  现在缓存中查询,如果有直接返回,否则查询数据库
    	public Comment findCommentById(Integer id){
    		Object o = redisTemplate.opsForValue().get("comment_"+id);
    		if(o!=null){
    			//有,直接返回
    			return (Comment)o;
    		}else{
    			//没有,数据库中查询
    			Optional<Comment> comment = commentRepository.findById(id);
    			if(comment.isPresent()){
    				Comment model = comment.get();
    				redisTemplate.opsForValue().set("comment_"+id, model, 1, TimeUnit.DAYS);
    				return model;
    			}
    		}
    		return null;
    	}
    	
    	//更新方法
    	public Comment updateComment(Comment comment){
    		commentRepository.updateComment(comment.getAuthor(),comment.getId());
    		//将更新数据进行缓存更新
    		redisTemplate.opsForValue().set("comment_"+comment.getId(), comment, 1, TimeUnit.DAYS);
    		return comment;
    	}
    	
    	//删除方法
    	public void deleteComment(Integer id){
    		commentRepository.deleteById(id);
    		redisTemplate.delete("comment_"+id)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    基于API的Redis缓存实现的相关配置,基于API的Redis缓存实现不需要@EnableCaching注解开启基于注解的缓存支持,所以这里可以选择将添加在项目启动类上的@EnableCaching进行删除或注释

    3 自定义Redis缓存序列化机制

    刚刚完成了SpringBoot整合Redis进行数据的缓存管理,但缓存管理的实体类数据使用的是JDK序列化机制,不便于使用可视化管理工具进行查看和管理。
    在这里插入图片描述接下来分别针对基于注解的Redis缓存实现和基于API的Redis缓存实现中的数据序列化机制进行介绍,并自定义JSON格式的数据序列化机制进行数据缓存管理

    3.1 自定义RedisTemplate

    3.1.1 Redis API默认序列化机制

    基于API的Redis缓存实现是使用RedisTemplate模板进行数据缓存操作的,这里打开RedisTemplate类,查看该类的源码信息

    public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
    	//声明了key、value的各种序列化方式,初始值为空
    	@Nullable
        private RedisSerializer keySerializer = null;
        @Nullable
        private RedisSerializer valueSerializer = null;
        @Nullable
        private RedisSerializer hashKeySerializer = null;
        @Nullable
        private RedisSerializer hashValueSerializer = null;
        ...
        //进行默认序列化设置,设置为JDK序列化方式
        public void afterPropertiesSet() {
            super.afterPropertiesSet();
            boolean defaultUsed = false;
            if (this.defaultSerializer == null) {
                this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
            }
            ...
        }
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    从上述RedisTemplate核心源码可以看出,在RedisTemplate内部声明了缓存数据key、value的各种序列化方式,且初始值都为空;在afterPropertiesSet()方法中,判断如果默认序列化参数defaultSerializer 为空,将数据的默认序列化方式设置为JdkSerializationRedisSerializer
    根据上述源码信息的分析,可以得出以下两个重要的结论:
    (1)使用RedisTemplate进行Redis数据缓存操作时,内部默认使用的是JdkSerializationRedisSerializer序列化方式,所以进行数据缓存的实体类必须实现JDK自带的序列化接口(如Serializable);
    (2)使用RedisTemplate进行Redis数据缓存操作时,如果自定义了缓存序列化方式defaultSerializer,那么将使用自定义的序列化方式。
    另外,在RedisTemplate类源码中,看到缓存数据key、value的各种序列化类型都是RedisSerializer。进入RedisSerializer源码查看RedisSerializer支持的序列化方式(进入该类后,使用Ctrl+Alt+左键单击类名查看)
    RedisSerializer的实现类
    从上图中可以看出,RedisSerializer是一个Redis序列化接口,默认有6个实现类,这6个实现类代表了6种不同的数据序列化方式。其中,JdkSerializationRedisSerializer是JDK自带的,也是RedisTemplate内部默认使用的数据序列化方式,我们可以根据需要选择其他支持的序列化方式(例如JSON方式)

    3.1.2 自定义RedisTemplate序列化机制

    在项目引入Redis依赖后,SpringBoot提供的RedisAutoConfiguration自动配置会生效。打开RedisAutoConfiguration类,查看内部源码关于RedisTemplate的定义方式

    public class RedisAutoConfiguration {
    
        @Bean
        @ConditionalOnMissingBean(
            name = {"redisTemplate"}
        )
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从上述RedisAutoConfiguration核心源码中可以看出,在Redis自动配置类中,通过Redis连接工厂RedisConnectionFactory初始化了一个RedisTemplate;该类上方添加了@ConditionalOnMissingBean注解(顾名思义,当某个Bean不存在时生效),用来表明如果开发者自定义了一个名为redisTemplate的Bean,则该默认初始化的RedisTemplate不会生效。
    如果想要使用自定义序列化方式的RedisTemplate进行数据缓存操作,可以参考上述核心代码创建一个名为redisTemplate的Bean组件,并在该组件中设置对应的序列化方式即可
    接下来,在项目中创建名为com.lxsh.config的包,在该包下创建一个Redis自定义配置类RedisConfig,并按照上述思路自定义名为redisTemplate的Bean组件

    @Configuration
    public class RedisConfig {
    	//自定义一个RedisTemplate
    	@Bean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            //创建JSON格式序列对象,对缓存数据的key和value进行转换
            Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    		//解决查询缓存转换异常的问题
    		ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
            //设置redisTemplate模板API的序列化方式为JSON
            template.setDefaultSerializer(jackson2JsonRedisSerializer);
    
            return template;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    通过@Configuration注解定义了一个RedisConfig配置类,并使用@Bean注解注入了一个默认名称为方法名的redisTemplate组件(注意,该Bean组件名称必须是redisTemplate)。在定义的Bean组件中,自定义了一个RedisTemplate,使用自定义的Jackson2JsonRedisSerializer数据序列化方式;在定制序列化方式中,定义了一个ObjectMapper用于进行数据转换设置。

    3.2 自定义RedisCacheManager

    刚刚针对基于API方式的RedisTemplate进行了自定义序列化方式的改进,从而实现了JSON序列化方式缓存数据,但是这种自定义的RedisTemplate对于基于注解的Redis缓存来说,是没有作用的。
    接下来,针对基于注解的Redis缓存机制和自定义序列化方式进行说明

    3.2.1 Redis注解默认序列化机制

    打开SpringBoot整合Redis组件提供的缓存自动配置类,RedisCacheConfiguration(org.springframework.boot.autoconfigure.cache包下),查看该类的源码信息,其核心代码如下

    @Configuration(
        proxyBeanMethods = false
    )
    class RedisCacheConfiguration {
    	@Bean
        RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
            RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
            List<String> cacheNames = cacheProperties.getCacheNames();
            if (!cacheNames.isEmpty()) {
                builder.initialCacheNames(new LinkedHashSet(cacheNames));
            }
    
            if (cacheProperties.getRedis().isEnableStatistics()) {
                builder.enableStatistics();
            }
    
            redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> {
                customizer.customize(builder);
            });
            return (RedisCacheManager)cacheManagerCustomizers.customize(builder.build());
        }
        
        private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(CacheProperties cacheProperties, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ClassLoader classLoader) {
            return (org.springframework.data.redis.cache.RedisCacheConfiguration)redisCacheConfiguration.getIfAvailable(() -> {
                return this.createConfiguration(cacheProperties, classLoader);
            });
        }
        
        private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(CacheProperties cacheProperties, ClassLoader classLoader) {
            Redis redisProperties = cacheProperties.getRedis();
            org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig();
            config = config.serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
            ...
            return config;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    从上述核心源码中可以看出,同RedisTemplate核心源码类似,RedisCacheConfiguration内部同样通过Redis连接工厂RedisConnectionFactory定义了一个缓存管理器RedisCacheManager,同时定制RedisCacheManager时,也默认使用JdkSerializationRedisSerializer序列化方式。
    如果想要使用自定义序列化方式的RedisCacheManager进行数据缓存操作,可以参考上述核心代码创建一个名为cacheManager的Bean组件,并在该组件中设置对应的序列化方式即可。

    • 注意,在Spring Boot 2.X版本中,RedisCacheManager时单独进行构建的。因此,在SpringBoot 2.X版本中,对RedisTemplate进行自定义序列化机制构建后,仍然无法对RedisCacheManager内部序列化机制进行覆盖(这也就解释了基于注解的Redis缓存实现仍然会使用JDK默认序列化机制的原因),想要基于注解的Redis缓存实现也使用自定义序列化机制,需要自定义RedisCacheManager

    3.2.2 自定义RedisCacheManager

    在项目的Redis配置类RedisConfig中,按照上一步分析的定制方法自定义名为cacheManager的Bean组件

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    	//创建JSON格式序列对象,对缓存数据的key和value进行转换
    	RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    	//解决查询缓存转换异常的问题
    	ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //定制缓存数据序列化方式及时效
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofDays(1))
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
                    .disableCachingNullValues();
    	RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                    .cacheDefaults(config).build();
        return cacheManager;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上述代码中,在RedisConfig配置类中使用@Bean注解注入了一个默认名称为方法名的cacheManager组件。在定义的Bean组件中,通过RedisCacheConfiguration对缓存数据的key和value分别进行了序列化方式的定制,其中缓存数据的key定制为StringRedisSerializer(即String格式),而value定制为了Jackson2JsonRedisSerializer(即JSON格式),同时还使用entryTtl(Duration.ofDays(1))方法将缓存数据有效期设置为1天,disableCachingNullValues()表示不缓存空值。

  • 相关阅读:
    论文阅读【时间序列】TimeMixer (ICLR2024)
    Redis缓存问题
    [Redis] Spring Boot 使用Redis---RedisTemplate泛型约束乱码问题
    6.SSM-SpringBoot
    这几个图片格式转换工具可以收藏
    CLR via C#-托管堆和垃圾回收
    鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Button)
    Flink开发环境搭建与提交运行Flink应用程序
    荧光素标记氨基酸,异硫氰酸荧光素FITC标记D-天冬氨酸;FITC-D-Aspartic acid
    【八股系列】react里组件通信有几种方式,分别怎样进行通信?
  • 原文地址:https://blog.csdn.net/weixin_44152160/article/details/126214975