Spring Boot缓存管理

Spring Boot默认缓存管理

基础环境搭建

(1)准备数据,使用之前的springbootdata数据库
(2)创建一个Spring Boot项目,引入SQL模块的JPA依赖、MySQL依赖和Web模块中的Web依赖。
(3)编写实体类Comment

package com.itheima.domain;

import javax.persistence.*;

@Entity(name = "t_comment")
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String content;
    private String author;
    @Column(name = "a_id")
    private Integer aId;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getAuthor() {
        return author;
    }

    public void setAutor(String author) {
        this.author = author;
    }

    public Integer getaId() {
        return aId;
    }

    public void setaId(Integer aId) {
        this.aId = aId;
    }

    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                ", content='" + content + '\'' +
                ", author='" + author + '\'' +
                ", aId=" + aId +
                '}';
    }
}

(4)编写数据库操作的Repository接口文件。创建一个com.itheima.repository包,并在该包下创建一个用于操作的Comment实体的Repository接口,

package com.itheima.repository;

import com.itheima.domain.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import javax.transaction.Transactional;

public interface CommentRepository extends JpaRepository<Comment,Integer> {
    @Transactional
    @Modifying
    @Query("UPDATE t_comment c SET c.author = ?1 WHERE c.id=?2")
    public int updateComment(String author,Integer id);
}

(5)编写业务操作类Service文件,创建一个com.itheima.service的包,并在改包下创建一个用于Comment相关业务操作的Service实体类

package com.itheima.service;

import com.itheima.domain.Comment;
import com.itheima.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class CommentService {
    @Autowired
    private CommentRepository commentRepository;
    public Comment findById(int comment_id){
        Optional<Comment>optional=commentRepository.findById(comment_id);
        if(optional.isPresent()){
            return optional.get();
        }
        return null;
    }
    public Comment updateComment(Comment comment){
        commentRepository.updateComment(comment.getAuthor(),comment.getaId());
        return comment;
    }
    public void deleteComment(int comment_id){
        commentRepository.deleteById(comment_id);
    }
}

(6)编写Web层的Controller文件,在com.itheima.controller包,并在该包下创建一个用于Comment访问控制的Controller实体类。

package com.itheima.controller;

import com.itheima.domain.Comment;
import com.itheima.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CommentController {
    @Autowired
    private CommentService commentService;
    @GetMapping("/get/{id}")
    public Comment findById(@PathVariable("id") int comment_id){
        Comment comment=commentService.findById(comment_id);
        return comment;
    }
    @GetMapping("/update/{id}/{author}")
    public Comment updateCpmment(@PathVariable("id") int comment_id,@PathVariable("author") String author){
        Comment comment=commentService.findById(comment_id);
        comment.setAutor(author);
        Comment upadteComment=commentService.updateComment(comment);
        return upadteComment;
    }
    @GetMapping("/delete/{id}")
    public void deleteComment(@PathVariable("id") int comment_id){
        commentService.deleteComment(comment_id);
    }
}

(7)编写配置文件
在项目全局变量中添加

spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.show-sql=true

(8)项目测试,访问地址http://localhost:8080/get/1
然后刷新,
在这里插入图片描述
说明每一次刷新都执行了一次SQL语句查询。

Spring Boot默认缓存体验

(1)在Chapter06Application上添加@EnableCaching
在这里插入图片描述
(2)在CommentController上添加 @Cacheable(cacheNames = “comment”)
在这里插入图片描述

重启项目,然后访问http://localhost:8080/get/1,然后进行刷新
在这里插入图片描述
说明项目开启的默认缓存支持已经生效。

Spring Boot缓存注解介绍

@EnableCaching注解

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

@Cacheable注解

@Cacheable注解也是由Spring框架提供的,可以作用域类或方法,用于对方法的查询结果进行缓存查询,并将接过进行缓存,如果缓存中有数据,不进行方法查询,而是直接使用缓存数据。
@Cacheable注解提供了多个属性,用于对缓存查询进行相关配置。

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

(1)value/CacheNames属性
value和cacheNames属性作用相同,都是用于指定缓存的名称空间,可以同时指定多个名称空间,如果@Cacheable注解值配置value的一个属性,那么这两个属性名可以省略
(2)key属性
key属性的作用是指定缓存数据对应的唯一标识,默认使用注解标记的方法参数值,也可以使用SqEL表达式,缓存数据的本质是Map类型的数据,key用于指定唯一的标识,value用于指定缓存的数据。
如果缓存数据时,没有指定key属性,那么Spring Boot会通过generateKey(Object…params)方法参数生成key值。默认情况下如果generateKey()方法有一个参数,参数值就是key属性的值。
除了使用默认的key属性值外,还可以手动指定key属性值,或者使用Spring框架提供的SqEL表达式。

名称位置描述示例
methodNameroot对象当前被调用的方法名#root.methodName
methodroot对象当前被调用的方法#root.method.name
targetroot对象当前被调用的目标对象实例#root.target
targetClassroot对象当前被调用的目标对象的类#root.target.class
argsroot对象当前被调用的方法的参数列表#root.args[0]
cachesroot对象当前被调用方法的缓存列表#root.caches[0].name
ArgumentName执行上下文当前被调用的方法参数,可以用#参数名或者#a0、#p0的形式表示#comment_id、#a0、#p0
result执行上下文当前方法执行后的返回结果#result

(3)keyGenerator属性
可以Generator属性与key属性本质相同,都是用于指定缓存数据的key,只不过keyGenerator属性指定的不是具体的key值,而是key值的生成器规则,由其指定的生成器生成具体的key,使用keyGenerator属性与key属性二者选一。
(4)cacheManager/cacheResolve属性
cacheManager和cacheResolver属性分别用于指定缓存区管理器和缓存解析器。这两个也是二选一默认情况下不需要配置
(5)condition属性
condition属性用于对数据进行有条件的选择性存储,只有当指定条件为true时,才会查询进行缓存,可以使用SqEL表达式指定属性值。
(6)unless属性
unless属性的作用与condition属性相反,当指定的条件为true实现,方法的返回值不会被缓存,unless属性可以使用SqEL表达式指定。
(7)sync属性
sync属性表示数据缓存过程中是否使用异步模式,默认值为false。

@CacheEvict注解

@CacheEvict注解是由Spring框架提供的,可以作用于类或方法,该注解的作用是删除缓存数据。@CacheEvict注解的默认提供顺序是,先进行方法调用,然后清除缓存。
@CacheEvict注解提供了许多属性,这些属性与@Cacheable注解的属性基本相同,除此之外,@CacheEvic注解额外提供了两个特殊属性,allEntries和beforeInvocation
(1)allEntries属性
allEntries属性表示清除指定缓存空间中的所有缓存数据,默认值为false(默认只删除key对应的缓存数据)。
(2)beforeInvocation属性
beforeInvocation属性表示是否在方法执行之前进行缓存清除,默认值为false(即默认在执行方法之后在进行缓存清除)。

@Caching注解

如果处理复杂规则的数据缓存可以使用@Caching注解,该注解用于类或者方法。@Caching注解包含cacheable、put和evict三个属性,他们的作用等同于@Cacheable、@CachePut和@CacheEvict。
示例代码

@Caching(cacheable={@Cacheable(cacheNames="comment",key="#id")},put={@CachePut(cacheNames="coment" put="#result.author")})
public Comment getComment(int comment_id){
	return commentRepository.findById(Comment_id).get();
}

上述代码中,根据id执行查询操作,并将查询到的Comment对象进行缓存管理。

@CacheConfig注解

@CacheConfig注解作用于类,主要用于统筹管理类中使用@Cacheable、@CachePut和@CacheEvict注解标注的方法中的公共属性包括cacheNames、keyGenerator、cacheManager和CacheResolver。

Spring Boot整合Redis缓存实现

(1)添加Spring DataRedis依赖启动器,在pom.xml中加入DataRedis依赖启动器

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

(2)Redis服务连接配置,使用类似Redis的第三方缓存组件进行缓存管理时,缓存数据并不会像Spring Boot默认缓存管理那样存储在内存中,而是需要预先搭建类似Redis服务的数据仓库进行缓存存储。在项目全局配置文件application.properties中天剑Redis服务的连接配置。

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=

(3)使用@Cacheable、@CachePut、@CacheEvict注解定制缓存管理,对CommentService类中的方法进行修改。增加圈出的代码
在这里插入图片描述
(4)将缓存对象实现序列化,对Comment类进行改进,实现JDK自带的序列化接口Serializable

在这里插入图片描述
(5)重新启动chapter06项目,启动Redis,打开Redis客户端可视化工具连接Redis
访问http://localhost:8080/get/1
在这里插入图片描述
在这里插入图片描述
再访问http://localhost:8080/update/1/shitou
在这里插入图片描述
在这里插入图片描述
访问http://localhost:8080/delete/1
缓存信息没有了
在这里插入图片描述

基于APIDERedis缓存实现

(1)使用Redis API进行业务数据缓存管理,在com.itheima.service下创建一个类ApiCommentService

package com.itheima.service;

import com.itheima.domain.Comment;
import com.itheima.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Service
public class ApiCommentService {
    @Autowired
    private CommentRepository commentRepository;
    @Autowired
    private RedisTemplate redisTemplate;
    public Comment findById(int comment_id){
        Object object=redisTemplate.opsForValue().get("comment_"+comment_id);
        if(object!=null){
            return (Comment) object;
        }
        else {
            Optional<Comment> optional=commentRepository.findById(comment_id);
            if (optional.isPresent()){
                Comment comment=optional.get();
                redisTemplate.opsForValue().set("comment_"+comment_id,comment,1, TimeUnit.DAYS);
                return comment;
            }
            else {
                return null;
            }
        }
    }
    public Comment updateComment(Comment comment){
        commentRepository.updateComment(comment.getAuthor(), comment.getaId());
        redisTemplate.opsForValue().set("comment_"+comment.getId(),comment);
        return comment;
    }
    // 删除评论信息
    public void deleteComment(int comment_id){
        commentRepository.deleteById(comment_id);
        redisTemplate.delete("comment_"+comment_id);
    }
}

首先使用@Autowired注解将RedisTemplates作为组件注入Spring容器。
Redis API中的RedisTemplate的更多用法具体介绍如下:
(1)RedisTemplate是Spring Data Redis提供直接进行Redis操作的Java API,可以直接注入使用,相对于传统的Jedis更加简便。
(2)RedisTemplate可以操作<Object,Object>对象类型数据,而其子类StringRedisTemplate则是专门真的<String,String>字符串类型的数据进行操作。
(3)RedisTemplate类中提供了很多进行数据缓存操作的方法,可以进行数据缓存查询,换存更新、缓存修改、缓存删除以及设置缓存有效期等。
(4)上述示例中redisTemplate.opsForValue()。set(“comment_”+comment_id.comment,1,timeUnit.Days)设置缓存数据的同时,将缓存器=有效期设置为1天时间,还可已设置缓存有效期,在设置缓存数据。

redisTemplate.opsForValue().set("comment_"+comment_id,comment);
redisTemplate.expire("comment_"+comment_id,90,TimeUnit.SECONDS);

(2)编写Web访问层的Controller文件,在com.itheima.controller包下创建Controller实体类

package com.itheima.controller;

import com.itheima.domain.Comment;
import com.itheima.service.ApiCommentService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api")
public class ApiCommentController {
    @Autowired
    private ApiCommentService apiCommentService;
    @GetMapping("/get/{id}")
    public Comment findById(@PathVariable("id") int comment_id){
        Comment comment = apiCommentService.findById(comment_id);
        return comment;
    }

    @GetMapping("/update/{id}/{author}")
    public Comment updateComment(@PathVariable("id") int comment_id,
                                 @PathVariable("author") String author){
        Comment comment = apiCommentService.findById(comment_id);
        comment.setAuthor(author);
        Comment updateComment = apiCommentService.updateComment(comment);
        return updateComment;
    }

    @GetMapping("/delete/{id}")
    public void deleteComment(@PathVariable("id") int comment_id){
        apiCommentService.deleteComment(comment_id);
    }
}

@RequestMapping(“/api”)作用域ApiCommentController类,该类的所有方法都将映射到“/api”路径请求下。
(3)基于API的Redis缓存实现的相关配置。基于API的Redis缓存实现不需要@EnableCaching注解开启基于注解的缓存支持,所以这里可以选择将添加在项目启动类上的@EnableCaching进行删除或者注释。
另外,基于API的Redis缓存实现需要在Spring Boot项目的pom.xml文件中引入Redis依赖启动器,并在配置文件中进行Redis服务连接配置,同时为进行数据存储的COmment实体类实现序列化接口,这些配置于注解的Redis缓存实现操作步骤相同,并且已经实现,这里不再重复。
在Spring Boot项目中,完成基于API的Redis缓存配置后,下面就可以进行缓存查询、缓存更新和缓存删除的相关测试了。
相对使用注解的方式,使用Redis API进行缓存管理更加灵活,例如,手机验证码进行验证时,可以在缓存中设置验证等待时间。相比使用注解的方式进行缓存管理,使用Redis API的方式编写的代码量可能会更多。

自定义Redis缓存序列化机制

自定义RedisTemplate

Redis API默认序列化机制

打开RedisTemplate类,查看该类的源码信息
在这里插入图片描述
从RedisTemplate源码可以看出,RedisTemplate中声明了缓存数据key-value的各种序列化方式,且初始值都为空。在afterPropertiesSet()方法中,如果序列化参数defaultSerializer为null,组序列化方式为JdkSerializationRedisSerializer。
根据对源码的分析的出两个结论。
(1)使用RedisTemplate对Redis数据进行缓存操作时,内部使用的JDKSerialzation RedisSerializer序列化方式要求被序列化的实体类继承Serializable接口
(2)使用RedisTemplate时,如果没有特殊的设置,key和value都是使用defaultSerializer=new dkSerializationRedisSerializer()进行序列化的。

自定义RedisTemplate序列化机制

在项目引入Redis依赖后Spring Boot提供的RedisAutoConfiguration自动配置会生效。打开RedisAutoConfiguration类。核心代码如下所示:
在这里插入图片描述
在Redis自动配置类中,通过Redis连接工厂RedisConnectionFactory初始化了一个RedisTEMplate;RedisTemplate类上添加了@ConditionMissingBean注解,用来表明开发着如果自定义而一个名为RedisTemplate的Bean,则RedisTemplate会使用自定义的Bean。
示例:
(1)创建一个com.itheima.config的包,在该包下创建一个Redis自定义配置类

package com.itheima.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jacksonSial=new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om=new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSial.setObjectMapper(om);
        template.setDefaultSerializer(jacksonSial);
        return template;
    }
}

(2)重启项目,访问http://localhost:8080/api/get/3
在这里插入图片描述
在这里插入图片描述


版权声明:本文为qq_50834939原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。