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表达式。
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root对象 | 当前被调用的方法名 | #root.methodName |
method | root对象 | 当前被调用的方法 | #root.method.name |
target | root对象 | 当前被调用的目标对象实例 | #root.target |
targetClass | root对象 | 当前被调用的目标对象的类 | #root.target.class |
args | root对象 | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root对象 | 当前被调用方法的缓存列表 | #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