默认缓存管理
Spring框架支持透明地向应用程序添加缓存对缓存进行管理,其管理缓存的核心是将缓存应用于操作数据的方法,从而减少操作数据的执行次数,同时不会对程序本身造成任何干扰。 Spring Boot继承了Spring框架的缓存管理功能,通过使用@EnableCaching注解开启基于注解的缓存支持,Spring Boot就可以启动缓存管理的自动化配置。
1 基础环境搭建
1.准备数据
使用创建的springbootdata的数据库,该数据库有两个表t_article和t_comment
2.创建项目,功能编写
创建一个Controller,创建一个Service,创建一个dao。
1、创建项目并选择依赖
在Dependencies依赖选择项中添加SQL模块中的JPA依赖、MySQL依赖、Web模块中的Web依赖
然后Finish,创建项目
2、编写实体类
数据库t_comment表对应的实体类Comment,并使用JPA相关注解配置映射关系 Controller 层。
@Entity(name = "t_comment") // 设置实体类,并指定映射的表名
public class Comment {
@Id // 表明映射对应的主键id
@GeneratedValue(strategy = GenerationType.IDENTITY)// 设置主键自增策略
private Integer id;
private String content;
private String author;
@Column(name = "a_id") //指定映射的表字段名
private Integer aId;
// get/set/toString
}
3、编写数据库操作的Repository接口文件Dao层
public interface CommentRepository extends JpaRepository<Comment,Integer> {
//根据评论id修改评论作者author
@Transactional //把事物放在这里了
@Modifying // 删、改操作都是需要添加这个注解
@Query("update t_comment c set c.author = ?1 where c.id=?2")
public int updateComment(String author,Integer id);
}
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 comment1 = comment.get();
return comment1;
}
return null;
}
}
5、编写Controller层
@RestController
public class CommentController {
@Autowired
private CommentService commentService;
@RequestMapping("/findCommentById")
public Comment findCommentById(Integer id){
Comment commentById = commentService.findCommentById(id);
return commentById;
}
}
6、编写配置文件
在项目全局配置文件application.properties中编写对应的数据库连接配置
# MySQL数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=000000
#显示使用JPA进行数据库查询的SQL语句
spring.jpa.show-sql=true
#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case=true
#解决乱码
server.servlet.encoding.force-response=true
7、启动然后测试。
返回成功。
控制台发起一条sql查询请求
发现多请求几次就多几条查询(他们是到数据库去查询的)
是因为没有在Spring Boot项目中开启缓存管理。在没有缓存管理的情况下,虽然数据表中 的数据没有发生变化,但是每执行一次查询操作(本质是执行同样的SQL语句),都会访问一次数据库 并执行一次SQL语句
3、默认缓存体验
1、使用@EnableCaching注解开启基于注解的缓存支持
2、使用@Cacheable注解对数据操作方法进行缓存管理
将@Cacheable注解标注在Service类的查 询方法上,对查询结果进行缓存
是将查询结果Comment存放在Spring Boot默认缓存中名称为comment 的名称空间(namespace)中,对应缓存唯一标识 (即缓存数据对应的主键k)默认为方法参数comment_id的值
3、测试访问
在配置了Spring Boot默认注解后,重复进行同样的查询操作,发现,数据库只执行了一次SQL查询语句,说明项目开启的默认 缓存支持已经生效
底层结构:
在诸多的缓存自动配置类中, SpringBoot默认装配的是 SimpleCacheConfiguration , 他使用的 CacheManager 是 ConcurrentMapCacheManager, 使用 ConcurrentMap 当底层的数据结构,按照Cache的名字查询出Cache, 每一个Cache中存在多个k-v键值对,缓存值
1.@EnableCaching注解
@EnableCaching是由spring框架提供的,springboot框架对该注解进行了继承,该注解需要配置在类 上(在中,通常配置在项目启动类上),用于开启基于注解的缓存支持
2.@Cacheable注解
@Cacheable注解也是由spring框架提供的,可以作用于类或方法(通常用在数据查询方法上),用于 对方法结果进行缓存存储。注解的执行顺序是,先进行缓存查询,如果为空则进行方法查询,并将结果 进行缓存;如果缓存中有数据,不进行方法查询,而是直接使用缓存数据
3.@CachePut注解
是在目标方法执行完之后生效, @CachePut被使用于修改操作比较多,哪怕缓存中已经存在目标值了,但是这个 注解保证这个方法依然会执行,执行之后的结果被保存在缓存中
4.@CacheEvict注解
@CacheEvict注解是由Spring框架提供的,可以作用于类或方法(通常用在数据删除方法上),该注解 的作用是删除缓存数据。@CacheEvict注解的默认执行顺序是,先进行方法调用,然后将缓存进行清 除。
整合Redis缓存实现
使用基于注解的方式进行缓存管理
在Spring Boot默认缓存管理的基础上引入Redis缓存组件
1、在pom.xml文件中添加Spring Data Redis依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
添加了redis相关的启动器之后, SpringBoot会使用 RedisCacheConfigratioin 当做生效的自动 配置类进行缓存相关的自动装配,容器中使用的缓存管理器是 RedisCacheManager , 这个缓存管理器创建的Cache为 RedisCache , 进而操控redis进行数据的缓存
2、Redis服务连接配置 、启动
# Redis服务地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认
spring.redis.password=
3、对CommentService类中的方法进行修改
使用@Cacheable、@CachePut、@CacheEvict三个注解定制缓存管理,分别进行缓存存储、缓存更新和缓存删除
@Service
public class CommentService {
@Autowired
private CommentRepository commentRepository;
//cacheNames = "comment"缓存的空间就是命名空间,缓存的key就是参数id
//unless ="#return==null"如果返回的结果恒等于null,则不进行缓存
@Cacheable(cacheNames = "comment",unless = "#result==null") // 对查询结果进行缓存
public Comment findCommentById(Integer id) {
Optional<Comment> comment = commentRepository.findById(id);
if(comment.isPresent()){
Comment comment1 = comment.get();
return comment1;
}
return null;
}
// 完成修改后,通过CachePut进行缓存,再次把更新后的放到缓存中,那么就需要保持key值要一致
@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(int comment_id) {
commentRepository.deleteById(comment_id);
}
}
查询缓存@Cacheable注解中没有标记key值,将会使用默认参数值comment_id作为key进行 数据保存,
在进行缓存更新时必须使用同样的key;
同时在查询缓存@Cacheable注解中,定义了 “unless = “#result==null””表示查询结果为空不进行缓存。
4、编写Controller
@RestController
public class CommentController {
@Autowired
private CommentService commentService;
@RequestMapping("/findCommentById")
public Comment findCommentById(Integer id){
Comment commentById = commentService.findCommentById(id);
return commentById;
}
@RequestMapping("/updateComment")
public Comment updateComment(Comment comment){
Comment commentById = commentService.findCommentById(comment.getId());
commentById.setAuthor(comment.getAuthor());
Comment comment1 = commentService.updateComment(commentById);
return comment1;
}
@RequestMapping("/deleteComment")
public void deleteComment(Integer id){
commentService.deleteComment(id);
}
}
5、启动服务进行测试
原因是:实体类没有实现序列化。所以要给实体类实现序列化
6、启动测试
多次请求,在控制台都只有一条查询记录。说明是在缓存中取出的。
7、更新操作
基于API的Redis缓存实现
开发中常用的方式——基于API的Redis缓存实现。这种基于API的Redis缓存实现,需要在某种业务需求下通过 Redis提供的API调用相关方法实现数据缓存管理;同时,这种方法还可以手动管理缓存的有效期。
1、使用Redis API进行业务数据缓存管理。
在com.slin.service包下编写一个进行业务处理的 ApiCommentService
@Service
public class ApiCommentService {
@Autowired
private CommentRepository commentRepository;
// 通过redisTemplate来调用redis提供的一些API方法
@Autowired
private RedisTemplate redisTemplate;
// 使用API方式进行缓存,先去缓存中查找,缓存中有直接返回,如果没有就去数据库查
public Comment findCommentById(Integer id){
// 根据一个key去缓存中查询
Object o = redisTemplate.opsForValue().get("comment_" + id);
if(o!=null){
// 查询到数据,直接返回
return (Comment) o;
}else {
//缓存中没有,从数据库查询
Optional<Comment> byId = commentRepository.findById(id);
if(byId.isPresent()){
Comment comment = byId.get();
//将查询结果存入到缓存中,并设置有效期为1天
redisTemplate.opsForValue().set("comment_"+id,comment,1, TimeUnit.DAYS);
return comment;
}else {
return null;
}
}
}
public Comment updateComment(Comment comment) {
commentRepository.updateComment(comment.getAuthor(), comment.getId());
//更新数据后进行缓存更新(传递过来的comment作为缓存的值)
redisTemplate.opsForValue().set("comment_"+comment.getId(),comment);
return comment;
}
public void deleteComment(int comment_id) {
commentRepository.deleteById(comment_id);
redisTemplate.delete("comment_"+comment_id);
}
}
2、编写Web访问层Controller文件
@RestController
@RequestMapping("/api")
public class ApiCommentController {
@Autowired
private ApiCommentService commentService;
@RequestMapping("/findCommentById")
public Comment findCommentById(Integer id){
Comment commentById = commentService.findCommentById(id);
return commentById;
}
@RequestMapping("/updateComment")
public Comment updateComment(Comment comment){
Comment commentById = commentService.findCommentById(comment.getId());
commentById.setAuthor(comment.getAuthor());
Comment comment1 = commentService.updateComment(commentById);
return comment1;
}
@RequestMapping("/deleteComment")
public void deleteComment(Integer id){
commentService.deleteComment(id);
}
}
基于API的Redis缓存实现不需要@EnableCaching注解开启 基于注解的缓存支持,所以这里可以选择将添加在项目启动类上的@EnableCaching进行删除或者 注释
3、启动测试
多次请求,控制台只有一条sql语句,而且redis可视化工具有存数据。说明apiredis的缓存成功。
更新后,数据库也可以看到更新后的变化。
自定义Redis缓存序列化机制
要想自定义序列化机制,那么就要先了解默认的序列化机制。
1.Redis API默认序列化机制
基于API的Redis缓存实现是使用RedisTemplate模板进行数据缓存操作的,这里打开 RedisTemplate类,查看该类的源码信息
在RedisTemplate内部声明了缓存数据key、value的各 种序列化方式,且初始值都为空;在afterPropertiesSet()方法中,判断如果默认序列化参数 defaultSerializer为空,将数据的默认序列化方式设置为JdkSerializationRedisSerializer
(1)使用RedisTemplate进行Redis数据缓存操作时,内部默认使用的是 JdkSerializationRedisSerializer序列化方式,所以进行数据缓存的实体类必须实现JDK自带的序列化接 口(例如Serializable);
(2)使用RedisTemplate进行Redis数据缓存操作时,如果自定义了缓存序列化方式 defaultSerializer,那么将使用自定义的序列化方式。
RedisSerializer是一个Redis序列化接口,默认有6个实现类,这6个实现类代表了6种 不同的数据序列化方式。其中,JdkSerializationRedisSerializer是JDK自带的,也是RedisTemplate内 部默认使用的数据序列化方式,开发者可以根据需要选择其他支持的序列化方式(例如JSON方式)
2、自定义RedisTemplate序列化机制
在项目中引入Redis依赖后,Spring Boot提供的RedisAutoConfiguration自动配置会生效。打开 RedisAutoConfiguration类,查看内部源码中关于RedisTemplate的定义方式
如果要使用自定义序列化方式的RedisTemplate进行数据缓存操作,可以参考上述核心代码创建 一个名为redisTemplate的Bean组件,并在该组件中设置对应的序列化方式即可。
实现自定义配置
(将默认的jdk序列化方法,手动改为自定义的json序列化方式)
在项目中创建名为com.slin.config的包,在该包下创建一个Redis自定义配置类 RedisConfig,并按照上述思路自定义名为redisTemplate的Bean组件
@Configuration // 告诉Spring容器这个是配置类
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
// 可以用到RedisSerializer 的6中序列化方式中的1中。
// 创建JSON格式序列化对象,对缓存数据的key和value进行转换
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题(对象和json直接的转换)
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//设置redisTemplate模板API的序列化方式为json(将默认是DefaultSerializer进行修改)
template.setDefaultSerializer(jackson2JsonRedisSerializer);
return template;
}
}
启动服务,测试
多次请求,控制台只有一条sql。
3 自定义RedisCacheManager
刚刚针对基于 API方式的RedisTemplate进行了自定义序列化方式的改进,从而实现了JSON序列化方式缓存数据。
但是这种自定义的RedisTemplate对于基于注解的Redis缓存来说,是没有作用的。
接下来,针对基于注解的Redis缓存机制和自定义序列化方式进行讲解。
Redis注解默认序列化机制
打开Spring Boot整合Redis组件提供的缓存自动配置类 RedisCacheConfiguration(org.springframework.boot.autoconfigure.cache包下的),查看该类的源码信息,其核心代码如下
从上述核心源码中可以看出,同RedisTemplate核心源码类似,RedisCacheConfiguration内部同样通 过Redis连接工厂RedisConnectionFactory定义了一个缓存管理器RedisCacheManager;同时定制 RedisCacheManager时,也默认使用了JdkSerializationRedisSerializer序列化方式。
如果想要使用自定义序列化方式的RedisCacheManager进行数据缓存操作,可以参考上述核心代 码创建一个名为cacheManager的Bean组件,并在该组件中设置对应的序列化方式即可
注意,在Spring Boot 2.X版本中,RedisCacheManager是单独进行构建的。因此,在Spring Boot 2.X版本中,对RedisTemplate进行自定义序列化机制构建后,仍然无法对 RedisCacheManager内部默认序列化机制进行覆盖(这也就解释了基 于注解的Redis缓存实现仍 然会使用JDK默认序列化机制的原因),想要基于注解的Redis缓存实现也使用自定义序列化机 制,需要自定义RedisCacheManager
自定义RedisCacheManager
在项目的Redis配置类RedisConfig中,按照上一步分析的定制方法自定义名为cacheManager的 Bean组件
// Controller请求service时候,处理完数据调用缓存注解,缓存注解以json的方式存到缓存中。
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jacksonSeial =
new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 定制缓存数据序列化方式及时效
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1)) // 有效期为1天
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSeial)) // 对缓存的value以json的形式缓存
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
完成基于注解的Redis缓存管理器RedisCacheManager定制后,可以对该缓存管理器的效果进行 测试(使用自定义序列化机制的RedisCacheManager测试时,实体类可以不用实现序列化接口)
实现json存储。