Spring Boot缓存管理
Posted shi_zi_183
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot缓存管理相关的知识,希望对你有一定的参考价值。
Spring Boot缓存管理
缓存是分布式系统中的重要组件,主要解决数据库数据的高并发访问问题。在实际开发中,尤其是用户访问较大的网站,为了提高服务器访问性能、减少数据库的压力、提高用户体验,使用缓存显得尤为重要。Spring Boot对缓存提供了良好的支持。本章将针对Spring Boot的缓存管理进行介绍,并完成Spring Boot与Redis缓存中间件的整合使用。
Spring Boot默认缓存管理
Spring框架支持透明地向应用程序添加缓存并对缓存进行管理,其管理缓存的核心是将缓存应用于操作数据的方法中,从而减少操作数据的次数,同时不会对程序本身造成任何干扰。Spring Boot继承了Spring框架的缓存管理功能,通过使用@EnableCaching注解开启基于注解的缓存支持,Spring Boot可以启动缓存管理的自动化配置。
基础环境搭建
使用缓存的主要的目的是减少数据库的访问压力、提高用户体验,为此,治理我们结合数据库的访问操作对Spring Boot的缓存管理进行演示说明。下面我们先搭建演示Spring Boot缓存管理的基础环境。
准备数据
为了简便,这里使用第3章创建的springbootdata数据库,该数据库有两个表t_article和t_comment。这两个表预先插入了几条测试数据。
创建项目
1)创建Spring Boot项目,引入相关依赖。使用Spring Initlaizr方式创建一个名为chapter06的Spring Boot项目,在Dependencies依赖选项中添加SQL模块中的JPA依赖、mysql依赖,web模块中的Web依赖。
2)编写数据库表对应的实体类。在chapter06中创建名为com.example.chapter06.domain的包,在该包下创建针对数据库表t_comment编写对应的实体类Comment,并使用JPA相关注解配置映射关系。
Comment.java
package com.example.chapter06.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;
//省略属性getter和setter方法
//省略toString()方法
}
3)编写数据库操作的Repository接口文件。在chapter06中创建名为com.exmaple.chapter06.repository的包,并在该包下创建一个用于操作Comment实体的Repository接口,该接口继承自JapRepository,并且包含一个用于修改评论的方法updateComment()。
CommentRepository.java
package com.example.chapter06.Repository;
import com.example.chapter06.domain.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.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);
}
4)编写业务操作类Service文件。在chapter06中创建名为com.exmaple.chapter06.service的包,并在该包下创建一个用于Comment相关业务操作的Service实体类。
package com.example.chapter06.service;
import com.example.chapter06.Repository.CommentRepository;
import com.example.chapter06.domain.Comment;
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);
}
}
自定义了一个CommentService业务操作类,使用注入的CommentRepository实例对象完成对Comment评论数据的查询、修改和删除操作。
5)编写Web访问层Controller文件。在chapter06中创建名为com.exmaple.chapter06.controller的包,并在该包下创建一个用于Comment访问控制的Controller实体类。
CommentController.java
package com.example.chapter06.controller;
import com.example.chapter06.domain.Comment;
import com.example.chapter06.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 {
private final CommentService commentService;
@Autowired
public CommentController(CommentService commentService) {
this.commentService = commentService;
}
@GetMapping("/get/{id}")
public Comment findById(@PathVariable("id") int comment_id){
return commentService.findById(comment_id);
}
@GetMapping("/update/{id}/{author}")
public Comment updateComment(@PathVariable("id") int comment_id,@PathVariable("author") String author){
Comment comment=commentService.findById(comment_id);
comment.setAuthor(author);
return commentService.updateComment(comment);
}
@GetMapping("/delete/{id}")
public void deleteComment(@PathVariable("id") int comment_id){
commentService.deleteComment(comment_id);
}
}
编写配置文件
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
先对MySQL的连接进行了配置,然后配置了spring.jpa.show-sql=true用于展示操作的SQL语句,方便后续开启缓存时进行效果演示。
项目测试
启动chapter06项目,项目启动成功后,在浏览器上访问
查询id为1的用户评论信息,不论浏览器刷新多少次,访问同一个用户评论信息,页面的查询结果都会显示同一条数据,但是,浏览器每刷新一次,控制台会新输出一条SQL语句。
这是因为没有在Spring Boot项目中开启缓存管理。在没有缓存管理的情况下,虽然数据表中的数据没有发生变化,但是每执行一次查询操作,都会访问一次数据库并执行一次SQL语句。随着时间的积累,系统的二哟弄个胡不断增加,数据规模越来越大,数据库的操作会直接影响用户的使用体验,此时使用缓存往往是解决这一问题非常好的一种手动。
Spring Boot默认缓存体验
在前面搭建的Web应用基础上,开启Spring Boot默认支持的缓存,体验Spring Boot默认缓存的使用效果。
1)使用@EnableCaching注解开启基于注解的缓存支持,该注解通常会添加在项目启动类上。
Chapter06Application.java
package com.example.chapter06;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching //开启Spring Boot基于注解的缓存管理支持
public class Chapter06Application {
public static void main(String[] args) {
SpringApplication.run(Chapter06Application.class, args);
}
}
2)使用@Cacheable注解对数据操作方法进行缓存管理。将@Cacheable注解标注在Service类的查询方法上,对查询结果进行缓存。
package com.example.chapter06.service;
import com.example.chapter06.Repository.CommentRepository;
import com.example.chapter06.domain.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Cacheable(cacheNames = "comment")
@Service
public class CommentService {
private final CommentRepository commentRepository;
@Autowired
public CommentService(CommentRepository commentRepository) {
this.commentRepository = commentRepository;
}
public Comment findById(int comment_id){
Optional<Comment>optional=commentRepository.findById(comment_id);
return optional.orElse(null);
}
public Comment updateComment(Comment comment){
commentRepository.updateComment(comment.getAuthor(),comment.getaId());
return comment;
}
public void deleteComment(int comment_id){
commentRepository.deleteById(comment_id);
}
}
上述代码中,在CommentService类中的findById(int comment_id)方法上添加了查询缓存注解@Cacheable,该注解的作用是将查询结果comment存放在Spring Boot默认缓存中名称为comment的名称空间中,对应缓存的唯一标识默认为方法参数comment_id的值。
3)Spring Boot默认缓存测试。启动chapter06项目,浏览器访问,刷新几次。
无论刷新几次只有一句查询语句。
Spring Boot缓存注解介绍
@EnableCaching注解
@EnableCaching是由Spring框架提供的,Spring Boot框架对该注解进行了继承,该注解需要配置在类上(在Spring Boot,通过配置在项目启动类上),用于开启基于注解的缓存支持。
@Cacheable注解
Cacheable注解也是由Spring框架提供的,可以作用于类或方法,用于对方法的查询结果进行缓存存储。@Cacheable注解的执行顺序是,先进行缓存查询,如果为空则进行方法查询,并将结果进行缓存;如果缓存中有数据,不进行方法查询,而是直接使用缓存数据。
属性名 | 说明 |
---|---|
value/cacheNames | 制定缓存空间的名称,必配属性。这两个属性二选一使用 |
key | 制定缓存数据的key,默认使用方法参数值,可以使用SpEL表达式 |
keyGenerator | 制定缓存数据的key的生成器,与key属性二选一使用。 |
cacheManager | 指定缓存管理器 |
cacheResolver | 指定缓存解析器,与cacheManager属性二选一使用 |
condition | 指定在符合某条件下,进行数据缓存 |
unless | 指定在符合某条件下,不进行数据缓存 |
sync | 指定是否使用异步缓存。默认false |
下面我们针对@Cacheable注解的属性进行具体讲解
1)value/cacheNames属性
value和cacheNames属性作用相同,用于指定缓存的名称空间,可以同时指定多个名称空间(例如@Cacheable(cacheable=(“comment1”,“comment2”)))。如果注解只配置value或cacheNames的一个属性,那么这两个属性名可以省略。
2)key属性
key属性的作用是指定缓存数据对应的唯一标志,默认使用注解标记的方法参数值,也可以使用SpEL表达式。缓存数据的本质是Map类型的数据,key用于指定唯一的标识,value用于指定缓存的数据。
如果缓存数据时,没有指定key属性,Spring Boot默认提供的配置类SimpleKeyGenerator会通过generateKey(Object…params)方法参数生成key值。默认情况下,如果generateKey()方法用一个参数,参数值就是key属性的值;如果generateKey()方法没有参数,那么key属性是一个空参的SimpleKey[]对象,如果由多个参数,那么key属性是一个带参的SimpleKey[Params1,[Params2]]对象。
Cache缓存支持的SpEL表达式及说明
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root对象 | 当前被调用的方法名 | #root.methodName |
method | root对象 | 当前被调用的方法 | #root.method.name |
target | root对象 | 当前被调用的目标对象实例 | #root.target |
args | root对象 | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root对象 | 当前被调用的方法的缓存列表 | #root.caches[0].name |
ArgumentName | 执行上下文 | 当前被调用的方法参数,可以用#参数名或者#a0、#p0的形式标识(0表示参数索引,从0开始) | #comment_id,#a0,#p0 |
result | 执行上下文 | 当前方法执行后的返回结果 | #result |
3)keyGenerator属性
keyGenerator属性与key属性本质作用相同,都是用于指定缓存数据的key,只不过keyGenerator属性指定的不是具体的key值,而是key值的生成器规则,由其中指定的生成器生成具体的key。使用时,keyGenertor属性与key属性要二者选一。
4)cacheManager/cacheResolver属性
cacheManager和cacheResolver属性分别用于指定缓存管理器和缓存解析器,这两个属性也是二选一使用,默认情况不需要配置,如果存在多个缓存管理器,可以使用两个属性分别制定。
5)condition属性
condition属性用于对数据进行有条件的选择性存储,只有当指定条件为true时才会对查询结果进行缓存,可以使用SpEL表达式制定属性值。例如@Cacheable(cacheNames=“comment”,condition="#comment_id>10")表示方法参数comment_id的值大于10才会对结果进行缓存。
6)unless属性
unless属性的作用与condition属性相反,当制定的条件为true时,方法的返回值不会被缓存。unless属性可以使用SpEL表达式指定。例如@Cacheable(cacheNames=“comment”,unless="#result==null")表示只有查询结果不为空才会对结果数据进行缓存存储。
7)sync属性
sync属性表示数据缓存过程中是否使用异步模式,默认值false
CachePut注解
@CachePut注解是由Spring框架提供的,可以作用于类或方法(通常用在数据更新方法上),该注解的作用是更新缓存数据。@CachePut注解的执行顺序是,先进行方法调用,然后将方法结果更新到缓存中。
@CachePut注解也提供了多个属性,这些属性与@Cacheable注解的属性完全相同。
CacheEvict注解
CacheEvict注解是由Spring框架提供的,可以作用于类或方法(通常用数据删除方法上),该注解的作用是删除缓存数据。@CacheEvict注解的默认执行顺序是,先进行方法调用,然后清除缓存。
@CacheEvict注解提供了多个属性,这些属性于@Cacheable注解的属性基本相同。除此之外,@CacheEvict额外提供了两个特殊属性allEntrier和beforeInvacation
1)allEntries属性
表示是否清除指定缓存空间中的所有缓存数据,默认值为false(即默认只删除指定key对应的缓存数据)。例如@CacheEvict(cacheNames=“comment”,allEntrier=true)表示方法执行后会删除缓存空间comment中所有的数据。
2)beforInvocation属性
表示是否在方法执行之前进行缓存清除,默认值为false(即默认在执行方法后再进行缓存清除)。例如@CacheEvict(cacheNames=“comment”,beforInvacation=true)表示会在方法执行之前进行缓存清除。
需要注意的是,如果将@CacheEvict注解的beforeInvocation属性设置为true,会存在一定弊端。例如在进行数据删除的方法中发生了异常,这会导致实际数据并没有被删除,但是缓存数据却被提前清除了。
Caching注解
如果处理复杂规则的数据缓存可以使用@Caching注解,该注解作用于类或者方法。@Caching注解包含cacheable、put和evict三个属性,它们的作用等同于@Cacheable、@CachePut和@CacheEvict。
@Caching(cacheable={@Cacheable(cacheNames="comment",key="#id"}),put={@CachePut(cacheNames="comment",key="#result.author")})
public Comment getComment(int comment_id){
return commentRepository.findById(comment_id).get();
}
上述代码中,根据id执行查询操作,并将查询到的Comment对象进行缓存管理。从代码中可以看出,@Caching注解作用域getComment()方法上,并在@Caching注解中使用了cacheable和put两个属性,并且cacheable和put两个属性嵌套引入@Cacheable和@CachePut两个注解,在两个注解中分别使用#id和@result.author缓存key的值。
CacheConfig注解
@CacheConfig注解作用于类,注意用于统筹管理类中所有使用@Cacheable、@CachePut和@CacheEvict注解的方法中的公共属性,这些公共属性包括cacheNames、keyGenerator、cacheManager和cacheResolver
@CacheConfig(cacheNames="comment")
@Service
public class CommentService{
@Autowired
private CommentRepository commentRepository;
@Cacheable
public Comment findById(int comment_id){
Comment comment=commentRepository.findById(comment_id).get();
return comment;
}
}
上述代码中,CommentService类上标注了@CacheConfig注解,同时使用cacheName属性将缓存空间同一设置为comment,这样在该类中所有方法上使用缓存注解时可以省略cacheName属性。
注:如果在类上使用了@CacheConfig注解定义了某个属性,同时又在该类方法中使用缓存注解定义了相同的属性,那么该属性值会使用就近验证,以方法上注解为准。
Spring Boot整合Redis缓存实现
Spring Boot支持的缓存组件
在Spring Boot中,数据的管理存储依赖于Spring框架中cache相关的org.springframework.cache.Cache
和org.springframework.cache.CacheManager
缓存管理器接口。如果程序中没有定义类型为cacheManager的Bean组件或者是名为cacheResolver的cacheResolver缓存解析器,Spring Boot将尝试选择并启用以下缓存组件。
- Generic
- JCache(JSR-107)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Simple
上面我们按照Spring Boot缓存组件的加载顺序列举了支持的9种缓存组件,在项目中添加某个缓存管理组件后,Spring Boot项目会选择并启用对应的缓存管理器。如果项目中同时添加了多个缓存组件,且没有指定缓存管理器或者缓存解析器,那么Spring Boot会优先启动指定的缓存组件并进行缓存管理。
之前我们讲解Spring Boot默认缓存管理中,没有添加任何缓存管理组件却能实现缓存管理。这是因为开启缓存管理后,Spring Boot会按照上述列表顺序查找有效的缓存组件进行缓存管理,如果没有任何缓存组件,会默认使用最后一个Simple缓存组件进行管理。Simple缓存组件是Spring Boot默认的缓存管理组件,它默认使用内存中的ConcurrentHashMap进行缓存存储,所以在没有任何第三方缓存组件的情况下也可以实现内存中的缓存管理。
基于注解的Redis缓存实现
1)添加Spring Data Redis依赖启动器。在chapter06项目的pom.xml文件中添加Spring Data Redis依赖启动器。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2)Redis服务连接配置。使用类似Redis的第三方缓存组件进行缓存管理时,缓存数据并不是想Spring Boot默认缓存管理那样存储在内存中,而是需要预先搭建类似Redis服务的数据仓库进行缓存存储。所以首先需要安装并开启Redis服务。配置全局配置文件。
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
3)使用@Cacheable、@CachePut、@CacheEvict注解定制缓存管理。对CommentService类中的方法进行修改,使用@Cacheable、@CachePut、@CacheEvict3个注解定制缓存管理,分别演示数据的存储、更新、删除。
CommentService.java
package com.example.chapter06.service;
import com.example.chapter06.Repository.CommentRepository;
import com.example.chapter06.domain.Comment;
import net.bytebuddy.build.CachedReturnPlugin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Optional;
@CacheConfig(cacheNames = "comment")
@Service
public class CommentService {
private final CommentRepository commentRepository;
@Autowired
public CommentService(CommentRepository commentRepository) {
this.commentRepository = commentRepository;
}
@Cacheable(unless = "#result==null")
public Comment findById(int comment_id){
Optional<Comment>optional=commentRepository.findById(comment_id);
return optional.orElse(null);
}
@CachePut(key = "result.id")
public Comment updateComment(Comment comment){
commentRepository.updateComment(comment.getAuthor(),comment.getaId());
return comment;
}
@CacheEvict
public void deleteComment(int comment_id){
commentRepository.deleteById(comment_id);
}
}
4)基于注解的Redis查询缓存测试。通过前面的操作,我们已经在项目中添加了Redis缓存的依赖和Redis服务的连接配置,并且项目中已经使用@EnableCaching开启了基于注解的缓存管理,下面就可以直接启动项目进行缓存测试。
启动chapter06项目,项目启动成功后,通过浏览器"http://localhost:8080/get/1"查询id为1的用户评论信息,会发现浏览器数据响应错误,同时控制台出现异常信息。
查询用户评论信息Comment时执行了相应的SQL语句,但是在进行缓存存储时出现了IllegalArgumentException非法参数异常,提示信息要求对应Comment实体类必须实现序列化。
5)将缓存对象实现序列化。通过前面的异常错误提示发现,在对实体类对象进行缓存存储时必须先实现序列化(一些基本数据类型不需要序列化,因为内部已经默认实现了序列化接口),否则会出现缓存异常,导致程序无法正常执行。下面我们将进行缓存存储的Comment类进行改进,实现JDK自带的序列化接口Serializable。
Comment.java
package com.example.chapter06.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity(name = "t_comment")
public class Comment implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String content;
private String author;
@Column(name = "a_id")
private Integer aI以上是关于Spring Boot缓存管理的主要内容,如果未能解决你的问题,请参考以下文章