SpringBoot缓存之基于API的Redis缓存实现
在 SpringBoot 整合 Redis 缓存实现中,除了基于注解形式的 Redis 缓存实现外,还有一种开发中常用的方式——基于 API 的 Redis 缓存实现。
数据准备
--
-- 数据库: `springbootdata`
--
-- --------------------------------------------------------
--
-- 表的结构 `t_article`
--
CREATE TABLE `t_article` (
`id` int(20) NOT NULL COMMENT '文章id',
`title` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文章标题',
`content` longtext COLLATE utf8mb4_unicode_ci COMMENT '文章内容'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章表';
--
-- 转存表中的数据 `t_article`
--
INSERT INTO `t_article` (`id`, `title`, `content`) VALUES
(1, 'Spring Boot 基础入门', '从入门到精通讲解...'),
(2, 'Spring Cloud 基础入门', '从入门到放弃...'),
(3, '点什么外卖呢', '老梁干挑面吧'),
(4, '肯德基疯狂星期四', '不是很便宜啊,炒作吧!');
-- --------------------------------------------------------
--
-- 表的结构 `t_comment`
--
CREATE TABLE `t_comment` (
`id` int(20) NOT NULL COMMENT '评论id',
`content` longtext COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '评论内容',
`author` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '评论作者',
`a_id` int(20) DEFAULT NULL COMMENT '文章关联的id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评论表';
--
-- 转存表中的数据 `t_comment`
--
INSERT INTO `t_comment` (`id`, `content`, `author`, `a_id`) VALUES
(1,'写得什么玩意', '张三', 4)
(2, '你好帅', 'tom', 1),
(3, '很详细', 'kitty', 1),
(4, '他吃肯德基从来不是星期四', '马冬梅', 4)
(5, '这是网红店吧', '李四', 3)
(6, '写的不错喔,有前途', '周杰伦', 2);
--
-- 转储表的索引
--
--
-- 表的索引 `t_article`
--
ALTER TABLE `t_article`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `t_comment`
--
ALTER TABLE `t_comment`
ADD PRIMARY KEY (`id`);
在 MySQL 数据库中创建以上数据。
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
创建实体类
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
@Entity(name = "t_comment") // 设置 ORM 实体类,并指定映射的表名
@Data
public class Comment implements Serializable {
@Id // 表明映射对应的主键 id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 设置主键自增策略
private Integer id;
private String content;
private String author;
@Column(name = "a_id") // 指定映射的表字段名
private Integer aId;
}
这里创建一个评论的实体类,默认情况下,数据表中的字段对应实体类对象的属性,也可以用@Column注解指定数据表的字段名。并且用 Serializable 接口来实现 JDK 序列化机制,否则查询缓存时 Redis 会报错。
创建数据Repository
import com.syuez.springcache.entity.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> {
/**
* 根据评论 id 修改评价作者 author
* @param author 评价作者
* @param id 评论 id
* @return 数据库受影响条目数量
*/
@Transactional
@Modifying
@Query("UPDATE t_comment c SET c.author = ?1 WHERE c.id = ?2")
public int updateComment(String author, Integer id);
}
编写数据库操作的 Repository 接口文件,该接口继承自 JpaRepository,其中<Comment, Integer>表示实体类和主键类型。
编写服务类
import com.syuez.springcache.entity.Comment;
import com.syuez.springcache.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 commentId) {
// 先从 Redis 缓存中查询数据
Object object = redisTemplate.opsForValue().get("comment_" + commentId);
if(object != null) {
return (Comment) object;
} else {
// 缓存中没有,就进入数据库查询
Optional<Comment> optional = commentRepository.findById(commentId);
if(optional.isPresent()) {
Comment comment = optional.get();
// 将查询结果进行缓存,并设置有效期为 1 分钟
redisTemplate.opsForValue().set("comment_" + commentId, comment, 1, TimeUnit.MINUTES);
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 commentId) {
commentRepository.deleteById(commentId);
// 删除数据后进行缓存删除
redisTemplate.delete("comment_" + commentId);
}
}
首先使用@Autowired注解将 RedisTemplate 作为组件注入 Spring 容器,然后定义了findById()、updateComment()、deleteComment()三个方法,分别用于查询缓存、更新缓存以及删除缓存。当对数据进行缓存管理时,为了避免与其他业务的缓存数据混淆,对 Comment 数据缓存管理时,手动设置了前缀comment_。
关于 Redis API 中的 RedisTemplate 的更多用法,具体介绍如下
- RedisTemplate 是 Spring Data Redis 提供的直接进行 Redis 操作的 Java API,可以直接注入,相对于传统的 Jedis 更加简便。
- RedisTemplate 可以操作
<Object, Object>对象类型数据,而其子类 StringRedisTemplate 则是专门针对<String, String>字符串类型的数据进行操作。 - RedisTemplate 类中提供了很多进行数据缓存操作的方法,可以进行数据缓存查询、缓存更新、缓存修改、缓存删除以及设置缓存有效期等等。
上述示例中,redisTemplate.opsForValue().set("comment_" + commentId, comment, 1, TimeUnit.MINUTES);设置缓存数据的同时,将缓存有效期设置为1天时间(倒数第一个参数还可以设置其他时间单位,如天、小时、分钟、秒等等);当然,还可以先设置缓存有效期,再设置缓存数据。
编写控制器
import com.syuez.springcache.entity.Comment;
import com.syuez.springcache.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 commentId) {
return apiCommentService.findById(commentId);
}
@GetMapping("/update/{id}/{author}")
public Comment updateComment(@PathVariable("id") int commentId, @PathVariable("author") String author) {
Comment comment = apiCommentService.findById(commentId);
comment.setAuthor(author);
return apiCommentService.updateComment(comment);
}
@GetMapping("/delete/{id}")
public void deleteComment(@PathVariable("id") int commentId) {
apiCommentService.deleteComment(commentId);
}
}
@RequestMapping("/api")作用于 ApiCommentController 类,该类的所有方法都将映射为/api路径下的请求。@Autowired用于装配 ApiCommentService 对象,方便调用 ApiCommentService 中的相关方法进行数据查询、修改和删除。
自定义 Redis 缓存序列化机制
虽然实现了 Spring Boot 整合 Redis 进行数据的缓存管理,但缓存管理的实体类数据使用的是 JDK 序列化机制,不便于使用可视化管理工具进行查看和管理(因为使用 JDK 序列化后,在 Redis 存储的缓存是以十六进制保存的)。这里我们通过自定义 RedisTemplate 和 自定义 JSON 格式的数据序列化机制进行数据缓存管理。
自定义 RedisTemplate
如果想要使用自定义序列化方式的 RedisTemplate 进行数据缓存操作,需要创建一个名为 redisTemplate 的 Bean 组件,并在该组件中设置对应的序列化方式。
创建一个 Redis 自定义配置类 RedisConfig。
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.*;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
@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<Object> jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
/*
enableDefaultTyping 方法从 2.10.0 开始标记为过期
https://segon.cn/jackson-objectmapper-enabledefaulttyping-deprecated.html
*/
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(objectMapper);
// 设置 RedisTemplate 模板 API 的序列化方式为 JSON
template.setDefaultSerializer(jacksonSeial);
return template;
}
}
上述代码使用@Configuration注解将 RedisConfig标注为一个配置类,使用@Bean注解注入一个默认名称为 redisTemplate的组件。在 Bean 组件中,使用自定义的 Jackson2JsonRedisSerializer数据序列化方式自定义一个 RedisTemplate,在定制序列化方式中,定义一个 ObjectMapper 用于进行数据转换设置。
效果测试
修改 application.properties
# MySQL Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root#admin
# Display SQL Query
spring.jpa.show-sql=true
# Hikari Configuration
# minimum number of idle connections maintained by HikariCP in a connection pool
spring.datasource.hikari.minimum-idle=4
# maximum pool size
spring.datasource.hikari.maximum-pool-size=16
# maximum idle time for connection
spring.datasource.hikari.idle-timeout=600000
# maximum lifetime in milliseconds of a connection in the pool after it is closed
spring.datasource.hikari.max-lifetime=1800000
# maximum number of milliseconds that a client will wait for a connection
spring.datasource.hikari.connection-timeout=30000
# default auto-commit behavior
spring.datasource.hikari.auto-commit =true
# Tomcat Port
server.port=18443
server.address=localhost
# Redis Configuration
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
启动项目,项目启动成功后,通过浏览器访问 http://localhost:18443/api/get/3
查看 id 为 3 的用户评论,并重复刷新浏览器查看同一条数据信息,查看控制台打印的 SQL 查询语句:

可以看出,执行findById()方法正确查询出用户评论信息 Comment;重复进行同样的查询操作,数据库只执行了一次 SQL 语句,说明定制的 Redis 缓存生效。
使用 Redis 客户端可视化管理工具查看缓存数据:

可以看出,执行findById方法查询出用户评论信息 Comment 正确存储到了 Redis 缓存中,且缓存到 Redis 的数据以 JSON 格式存储,说明自定义的 Redis API 模板工具生效了。
本文摘自《Spring Boot 企业级开发教程》