实验六 Spring Boot缓存管理
一、实验目的
1、了解MyBatis的基础知识
2、熟悉MyBatis的工作原理
3、掌握MyBatis入门程序的编写
二、实验内容
1、通过实际开发介绍,引入数据缓存管理的重要。
2、根据spring的基本应用,练习Spring Boot整合Redis实现缓存管理,并进行相关组件定制。
三、实验步骤
第一部分实验 Speing Boot缓存管理
(一)基础环境搭建
1.准备数据,创建项目
(1)创建Spring Boot项目,引入相关依赖
①这里使用之前创建的springbootdata的数据库,该数据库有两个表 t_article和t_comment,这两个表预先插入几条测试数据。
②使用Spring Initializr方式创建一个Spring Boot项目,在Dependencies依赖选择项中JPA依赖、MySQL依赖和Web依赖。
(2)编写数据库表对应的实体类
在com.lg.ch06文件夹下创建domain文件夹并在其中创建实体类Comment.java
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 setAuthor(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 +
'}';
}
}
(3)编写数据库操作的Repository接口文件CommentRepository
在com.sun.ch06文件夹下创建repository文件夹并在其中创建接口文件CommentRepository
import com.lg.ch06.domain.Comment;
import org.springframework.data.jpa.repository.*;
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文件CommentService,在该类中编写数据的查询、修改和删除操作
在com.lg.ch06文件夹下创建service文件夹并在其中创建CommentService.java
import com.lg.ch06.domain.Comment;
import com.lg.ch06.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);
}
}
(5)编写Web访问层Controller文件CommentController,使用注入的CommentService实例对象编写对Comment评论数据的查询、修改和删除方法。
在com.lg.ch06文件夹下创建controller文件夹并在其中创建CommentController.java文件
import com.lg.ch06.domain.Comment;
import com.lg.ch06.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@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 updateComment(@PathVariable("id") int comment_id,
@PathVariable("author") String author){
Comment comment = commentService.findById(comment_id);
comment.setAuthor(author);
Comment updateComment = commentService.updateComment(comment);
return updateComment;
}
@GetMapping("/delete/{id}")
public void deleteComment(@PathVariable("id") int comment_id){
commentService.deleteComment(comment_id);
}
}
2.编写配置文件application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234
spring.jpa.show-sql=true
3.项目测试
项目启动成功后,在浏览器上访问http://127.0.0.1:8080/get/1,浏览器每刷新一次,控制台会新输出一条SQL语句
(二)Spring Boot 默认缓存体验
1.使用@EnableCaching注解开启基于注解的缓存支持
修改Ch06Application.java文件
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@EnableCaching
@SpringBootApplication
public class Ch06Application {
public static void main(String[] args) {
SpringApplication.run(Ch06Application.class, args);
}
}
2.使用@Cacheable注解对数据操作方法进行缓存管理
修改service文件夹下的CommentService.java中findById方法
@Cacheable(cacheNames = "comment")
public Comment findById(int comment_id){
Optional<Comment> optional = commentRepository.findById(comment_id);
if(optional.isPresent()){
return optional.get();
}
return null;
}
3.Spring Boot默认缓存测试
项目启动成功后,在浏览器上访问http://127.0.0.1:8080/get/1,不论浏览器刷新多少次,页面的查询结果都会显示同一条数据
第二部分实验Spring Boot整合Redis缓存实现
(一)SpringBoot支持的缓存组件
1)Generic
2)JCache (JSR-107) (EhCache 3、Hazelcast、Infinispan等)
3)EhCache 2.x
4)Hazelcast
5)Infinispan
6)Couchbase
7)Redis
8)Caffeine
9)Simple(默认)
(二)基于注解的Redis缓存实现
1.添加Spring Data Redis 依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.Redis服务连接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
3.使用@Cacheable、@CachePut、@CacheEvict注解定制缓存管理
对CommentService类中的方法进行修改
修改service文件夹下的CommentService.java
import com.lg.ch06.domain.Comment;
import com.lg.ch06.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Service
public class CommentService {
@Autowired
private CommentRepository commentRepository;
@Cacheable(cacheNames = "comment",unless = "#result==null")
public Comment findById(int comment_id){
Optional<Comment> optional = commentRepository.findById(comment_id);
if(optional.isPresent()){
return optional.get();
}
return null;}
@CachePut(cacheNames = "comment",key = "#result.id")
public Comment updateComment(Comment comment){
commentRepository.updateComment(comment.getAuthor(), comment.getaId());
return comment;
}
@CacheEvict(cacheNames = "comment")
public void deleteComment(int comment_id){
commentRepository.deleteById(comment_id);
}
}
4.基于注解的Redis查询缓存测试
项目启动成功后,通过浏览器访问http://127.0.0.1:8080/get/1
5.将缓存对象实现序列化
修改domain下的Comment.java,添加Serializable接口
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 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 setAuthor(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 +
'}';
}
}
6.基于注解的Redis缓存查询测试
项目启动成功后,通过浏览器访问http://127.0.0.1:8080/get/1,并重复刷新浏览器查询同一条数据信息
重复进行同样的查询操作,数据库只执行了一次SQL语句
打开Redis客户端可视化管理工具Redis Desktop Manager连接本地启用的Redis服务
7.基于注解的Redis缓存更新测试
项目启动成功后,通过浏览器访问http://127.0.0.1:8080/update/1/shitou,接着,继续访问http://127.0.0.1:8080/get/1
执行updateComment()方法更新id为1的数据时执行了一条更新SQL语句,后续调用findById()方法查询id为1的用户评论信息时没有执行查询SQL语句。
8.基于注解的Redis缓存删除测试
项目启动成功后,通过浏览器访问http://127.0.0.1:8080/delete/1,接着,继续访http://127.0.0.1:8080/get/1
通过Redis客户端可视化管理工具Redis Desktop Manager查看对应数据删除后的缓存信息
(三)基于API的Redis缓存实现
1.使用Redis API 进行业务数据缓存管理
在service文件夹下编写一个进行业务处理的类ApiCommentService
import com.lg.ch06.domain.Comment;
import com.lg.ch06.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){
// 先从Redis缓存中查询数据
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();
// 将查询结果进行缓存,并设置有效期为1天
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);
}
}
2.编写Web访问层Controller文件
在controller下创建ApiCommentController.java
import com.lg.ch06.domain.Comment;
import com.lg.ch06.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);
}
}
3.基于API的Redis缓存实现的相关配置
(1)基于API的Redis缓存实现不需要@EnableCaching注解开启基于注解的缓存支持。
(2)基于API的Redis缓存实现需要在Spring Boot项目的pom.xml文件中引入Redis依赖启动器,并在配置文件中进行Redis服务连接配置,同时将进行数据存储的Comment实体类实现序列化接口。
(3)缓存测试与基于注解的Redis缓存实现的测试完全一样。
① 查询测试
项目启动成功后,通过浏览器访问http://127.0.0.1:8080/get/2,并重复刷新浏览器查询同一条数据信息
② 更新测试
项目启动成功后,通过浏览器访问http://127.0.0.1:8080/update/2/shitou,接着,继续访问http://127.0.0.1:8080/get/2
③ 删除测试
项目启动成功后,通过浏览器访问http://127.0.0.1:8080/delete/2,接着,继续访http://127.0.0.1:8080/get/2
第三部分实验 自定义Redis缓存序列化机制
(一)自定义Redis Template
1.Redis API 默认序列化机制
基于Redis API的Redis缓存实现是使用RedisTemplate模板进行数据缓存操作的,这里打开RedisTemplate类,查看源码可知:
(1)使用RedisTemplate进行Redis数据缓存操作时,内部默认使用的是JdkSerializationRedisSerializer序列化方式,所以进行数据缓存的实体类必须实现JDK自带的序列化接口(例如Serializable);
(2)使用RedisTemplate进行Redis数据缓存操作时,如果自定义了缓存序列化方式defaultSerializer,那么将使用自定义的序列化方式。
2.自定义RedisTemplate序列化机制
在项目中引入Redis依赖后,Spring Boot提供的RedisAutoConfiguration自动配置会生效。打开RedisAutoConfiguration类,查看内部源码中关于RedisTemplate的定义方式可知:
在Redis自动配置类中,通过Redis连接工厂RedisConnectionFactory初始化了一个RedisTemplate;该类上方添加了@ConditionalOnMissingBean注解(顾名思义,当某个Bean不存在时生效),用来表明如果开发者自定义了一个名为redisTemplate的Bean,则该默认初始化的RedisTemplate会被覆盖。
如果想要使用自定义序列化方式的RedisTemplate进行数据缓存操作,可以参考上述核心代码创建一个名为redisTemplate的Bean组件,并在该组件中设置对应的序列化方式即可。
在com.lg.ch06文件夹下创建config文件夹并在其中创建RedisConfig.java
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);
// 使用JSON格式序列化对象,对缓存数据key和value进行转换
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);
// 设置RedisTemplate模板API的序列化方式为JSON
template.setDefaultSerializer(jacksonSeial);
return template;
}
}
项目启动成功后,通过浏览器访问http://127.0.0.1:8080/api/get/3,并重复刷新浏览器查看同一条数据信息
执行findById()方法正确查询出用户评论信息Comment,重复进行同样的查询操作,数据库只执行了一次SQL语句
使用Redis客户端可视化管理工具Redis Desktop Manager查看缓存数据
缓存到Redis服务的数据已经使用了JSON格式存储展示
(二)自定义RedisCacheManager
1.Redis 注解默认序列化机制
(1)Spring Boot整合Redis组件提供的缓存自动配置类RedisCacheConfiguration,其内部是通过Redis连接工厂RedisConnectionFactory定义了一个缓存管理器RedisCacheManager;同时定制RedisCacheManager时,也默认使用了JdkSerializationRedisSerializer序列化方式。
(2)如果想要使用自定义序列化方式的RedisCacheManager进行数据缓存操作,可以创建一个名为cacheManager的Bean组件,并在该组件中设置对应的序列化方式即可。
2.自定义RedisCacheManager
在config下的RedisConfig中添加自定义组件
@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))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSeial))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
3.效果测试
项目启动成功后,通过浏览器访问http://localhost:8080/api/get/3,并重复刷新浏览器查看同一条数据信息
执行findById()方法正确查询出用户评论信息Comment,重复进行同样的查询操作,数据库只执行了一次SQL语句
使用Redis客户端可视化管理工具Redis Desktop Manager查看缓存数据
缓存到Redis服务的数据已经使用了JSON格式存储展示