Redis有很多使用场景,一个使用场景就是缓存数据库的数据。Redis作为一个内存数据库,存取数据的速度比传
统的数据库快得多。使用Redis缓存数据库数据,可以减轻系统对数据库的访问压力,及加快查询效率等好处。下
面讲解如何使用 SpringBoot + Redis
来缓存数据库数据(这里数据库使用MySql
)。
Spring支持多种缓存技术:RedisCacheManager
、EhCacheCacheManager
、GuavaCacheManager
等,使用之
前需要配置一个CacheManager的Bean。配置好之后使用三个注解来缓存数据:@Cacheable
,@CachePut
和
@CacheEvict
。这三个注解可以加Service层或Dao层的类名上或方法上(建议加在Service层的方法上),加上类上
表示所有方法支持该注解的缓存;三注解需要指定Key,以返回值作为value操作缓存服务。所以,如果加在Dao
层,当新增1行数据时,返回数字1,会将1缓存到Redis,而不是缓存新增的数据。
create database redis_cache_test;
use redis_cache_test;
CREATE TABLE `sys_user` (
`t_id` varchar(32) NOT NULL COMMENT 'ID编号',
`t_name` varchar(300) DEFAULT NULL COMMENT '用户姓名',
`t_age` int(11) DEFAULT NULL COMMENT '用户年龄',
PRIMARY KEY (`t_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `sys_user` VALUES ('ID0001', 'zsx1', '27');
INSERT INTO `sys_user` VALUES ('ID0002', 'zsx2', '27');
INSERT INTO `sys_user` VALUES ('ID0003', 'zsx3', '27');
INSERT INTO `sys_user` VALUES ('ID0004', 'zsx4', '27');
INSERT INTO `sys_user` VALUES ('ID0005', 'zsx5', '18');
INSERT INTO `sys_user` VALUES ('ID0006', 'zsx6', '12');
INSERT INTO `sys_user` VALUES ('ID0007', 'zsx7', '8');
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-redis-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-redis-cache</name>
<description>spring-boot-redis-cache</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000ms
logging.level.root=ERROR
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/redis_cache_test?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
package com.example.springbootrediscache.redis;
/**
* 简单的bean,对应DB的表
*/
public class User {
public User() {
}
public User(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
private String id;
private String name;
private int age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package com.example.springbootrediscache.redis;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Component;
import java.util.List;
@Mapper
@Component
public interface UserDao {
/**
* 插入数据
*
* @param bean
* @return
*/
@Insert("insert into sys_user "
+ "(t_id, t_name, t_age) "
+ "values "
+ "(#{id}, #{name}, ${age}) ")
int insertUser(User bean);
/**
* 查询所有数据
*
* @return
*/
@ResultMap("redisUserDaoResults")
@Select("select t_id, t_name, t_age "
+ "from sys_user ")
List<User> selectUser();
/**
* 根据id查询数据
*
* @param id
* @return
*/
@Select("select t_id, t_age, t_name "
+ "from sys_user "
+ "where t_id = #{id} ")
@Results(id = "redisUserDaoResults", value = {
@Result(property = "id", column = "t_id"),
@Result(property = "age", column = "t_age"),
@Result(property = "name", column = "t_name"),
})
User selectUserById(@Param("id") String id);
/**
* 根据id修改数据
*
* @param user
* @return
*/
@Update("update sys_user set "
+ "t_name = #{name}, "
+ "t_age = #{age} "
+ "where t_id = #{id} ")
int updateUser(User user);
/**
* 根据id删除数据
*
* @param id
* @return
*/
@Delete("delete from sys_user "
+ "where t_id = #{id} ")
int deleteUserById(@Param("id") String id);
}
可以在Dao和Service任何一处使用缓存
package com.example.springbootrediscache.redis;
import org.apache.ibatis.annotations.*;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Mapper
@CacheConfig(cacheNames = "users")
public interface RedisCacheUserDao {
// https://blog.csdn.net/f641385712/article/details/95169002
@Cacheable(key = "#a0")
@Select("select t_id, t_age, t_name "
+ "from sys_user "
+ "where t_id = #{id} ")
@Results(id = "redisUserDaoResults", value = {
@Result(property = "id", column = "t_id"),
@Result(property = "age", column = "t_age"),
@Result(property = "name", column = "t_name"),
})
User selectUserById(@Param("id") String id);
@Cacheable(key = "'list'")
@ResultMap("redisUserDaoResults")
@Select("select t_id, t_name, t_age "
+ "from sys_user ")
List<User> selectUser();
}
package com.example.springbootrediscache.redis;
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.List;
/**
* 指定默认缓存区
* 缓存区:key的前缀,与指定的key构成redis的key,如 user::10001
*/
@CacheConfig(cacheNames = "user")
@Service
public class RedisCacheUserService {
@Autowired
private UserDao dao;
/**
* @Cacheable 缓存有数据时,从缓存获取;没有数据时,将返回值保存到缓存中
* @Cacheable 一般在查询中使用
* 1) cacheNames 指定缓存区,没有配置使用@CacheConfig指定的缓存区
* 2) key 指定缓存区的key
* 3) 注解的值使用SpEL表达式
* eq ==
* lt <
* le <=
* gt >
* ge >=
*/
@Cacheable(cacheNames = "user", key = "#id")
public User selectUserById(String id) {
return dao.selectUserById(id);
}
@Cacheable(key = "'list'")
public List<User> selectUser() {
return dao.selectUser();
}
/**
* condition 满足条件缓存数据
*/
@Cacheable(key = "#id", condition = "#number ge 20") // >= 20
public User selectUserByIdWithCondition(String id, int number) {
return dao.selectUserById(id);
}
/**
* unless 满足条件时否决缓存数据
*/
@Cacheable(key = "#id", unless = "#number lt 20") // < 20
public User selectUserByIdWithUnless(String id, int number) {
return dao.selectUserById(id);
}
/**
* @CachePut 将返回值保存到缓存中
* @CachePut 一般在新增和修改中使用
*/
@CachePut(key = "#user.id")
public User insertUser(User user) {
dao.insertUser(user);
return user;
}
@CachePut(key = "#user.id", condition = "#user.age ge 20")
public User insertUserWithCondition(User user) {
dao.insertUser(user);
return user;
}
@CachePut(key = "#user.id")
public User updateUser(User user) {
dao.updateUser(user);
return user;
}
/**
* 根据key删除缓存区中的数据
*/
@CacheEvict(key = "#id")
public void deleteUserById(String id) {
dao.deleteUserById(id);
}
/**
* allEntries = true :删除整个缓存区的所有值,此时指定的key无效
* beforeInvocation = true :默认false,表示调用方法之后删除缓存数据;true时,在调用之前删除缓存数据(如方法出现异常)
*/
@CacheEvict(key = "#id", allEntries = true)
public void deleteUserByIdAndCleanCache(String id) {
dao.deleteUserById(id);
}
}
RedisCacheManager
的配置如下:
package com.example.springbootrediscache.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
@Slf4j
public class RedisConfig {
/**
* SpringBoot配置redis作为默认缓存工具
* SpringBoot 2.0 以上版本的配置
*/
@Bean
public CacheManager cacheManager(RedisTemplate<String, Object> template) {
RedisCacheConfiguration defaultCacheConfiguration =
RedisCacheConfiguration
.defaultCacheConfig()
// 设置key为String
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(template.getStringSerializer()))
// 设置value为自动转Json的Object
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(template.getValueSerializer()))
// 不缓存null
.disableCachingNullValues()
// 缓存数据保存1小时
.entryTtl(Duration.ofHours(1));
RedisCacheManager redisCacheManager =
RedisCacheManagerBuilder
// Redis 连接工厂
.fromConnectionFactory(template.getConnectionFactory())
// 缓存配置
.cacheDefaults(defaultCacheConfiguration)
// 配置同步修改或删除 put/evict
.transactionAware()
.build();
return redisCacheManager;
}
/**
* redis template<String, Object>
*/
@Bean(name = "template")
public RedisTemplate<String, Object> template(RedisConnectionFactory factory) {
log.info("调用自定义的Redis Template!!!");
// 创建RedisTemplate<String, Object>对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
// 定义Jackson2JsonRedisSerializer序列化对象
Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会报异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
StringRedisSerializer stringSerial = new StringRedisSerializer();
// redis key 序列化方式使用stringSerial
template.setKeySerializer(stringSerial);
// redis value 序列化方式使用jackson
template.setValueSerializer(jacksonSeial);
// redis hash key 序列化方式使用stringSerial
template.setHashKeySerializer(stringSerial);
// redis hash value 序列化方式使用jackson
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
return template;
}
/**
* 定义数据类型
* string hash list set zset
*/
/**
* redis string
*/
@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}
/**
* redis hash
*/
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
/**
* redis list
*/
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
/**
* redis set
*/
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
/**
* redis zset
*/
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
package com.example.springbootrediscache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootRedisCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootRedisCacheApplication.class, args);
}
}
mysql> select * from sys_user;
+--------+--------+-------+
| t_id | t_name | t_age |
+--------+--------+-------+
| ID0001 | zsx1 | 27 |
| ID0002 | zsx2 | 27 |
| ID0003 | zsx3 | 27 |
| ID0004 | zsx4 | 27 |
| ID0005 | zsx5 | 18 |
| ID0006 | zsx6 | 12 |
| ID0007 | zsx7 | 8 |
+--------+--------+-------+
7 rows in set
package com.example.springbootrediscache;
import com.example.springbootrediscache.redis.RedisCacheUserDao;
import com.example.springbootrediscache.redis.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@SpringBootTest(classes = SpringBootRedisCacheApplication.class)
@RunWith(SpringRunner.class)
public class RedisDaoCache {
@Autowired
private RedisCacheUserDao dao;
@Autowired
private ValueOperations<String, Object> redisString;
@Test
public void test001() {
System.out.println("redis before use : " + redisString.get("users::ID0001"));
System.out.println(dao.selectUserById("ID0001"));
System.out.println("redis after use : " + redisString.get("users::ID0001"));
}
@Test
public void test002() {
System.out.println(redisString.get("users::list"));
List<User> list = dao.selectUser();
System.out.println(list.size());
System.out.println(redisString.get("users::list"));
}
}
@Test
public void test001() {
System.out.println("redis before use : " + redisString.get("users::ID0001"));
System.out.println(dao.selectUserById("ID0001"));
System.out.println("redis after use : " + redisString.get("users::ID0001"));
}
redis before use : null
User [id=ID0001, name=zsx1, age=27]
redis after use : User [id=ID0001, name=zsx1, age=27]
@Test
public void test002() {
System.out.println(redisString.get("users::list"));
List<User> list = dao.selectUser();
System.out.println(list.size());
System.out.println(redisString.get("users::list"));
}
null
7
[User [id=ID0001, name=zsx1, age=27], User [id=ID0002, name=zsx2, age=27], User [id=ID0003, name=zsx3, age=27], User [id=ID0004, name=zsx4, age=27], User [id=ID0005, name=zsx5, age=18], User [id=ID0006, name=zsx6, age=12], User [id=ID0007, name=zsx7, age=8]]
package com.example.springbootrediscache;
import com.example.springbootrediscache.redis.RedisCacheUserService;
import com.example.springbootrediscache.redis.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@SpringBootTest(classes = SpringBootRedisCacheApplication.class)
@RunWith(SpringRunner.class)
public class RedisServiceCache {
@Autowired
private RedisCacheUserService service;
@Autowired
private ValueOperations<String, Object> redisString;
/**
* 测试 @Cacheable 注解,缓存bean
*/
@Test
public void test001() {
// redis before use : null
// User [id=ID0001, name=zsx, age=27]
// redis after use : User [id=ID0001, name=zsx, age=27]
System.out.println("redis before use : " + redisString.get("user::ID0001"));
System.out.println(service.selectUserById("ID0001"));
System.out.println("redis after use : " + redisString.get("user::ID0001"));
System.out.println();
// redis before use : User [id=ID0001, name=zsx, age=27]
// User [id=ID0001, name=zsx, age=27]
// redis after use : User [id=ID0001, name=zsx, age=27]
System.out.println("redis before use : " + redisString.get("user::ID0001"));
System.out.println(service.selectUserById("ID0001"));
System.out.println("redis after use : " + redisString.get("user::ID0001"));
System.out.println();
}
/**
* 测试 @Cacheable 注解,缓存list
* 'list':指定 list字符串作为key
*/
@Test
public void test002() {
// null
// 3
// [User [id=ID0001, name=zsx1, age=27], User [id=ID0002, name=zsx2, age=27], User [id=ID0003, name=zsx3, age=27]]
System.out.println(redisString.get("user::list"));
// 如果往数据库中插入数据,依然还是会从缓存中拿出,不会拿出最新的数据
List<User> list = service.selectUser();
System.out.println(list.size());
System.out.println(redisString.get("user::list"));
}
/**
* 测试 @Cacheable 注解的 condition : 满足条件时缓存数据
*/
@Test
public void test003() {
// User [id=ID0002, name=zsx2, age=27]
// redis data[ID0002] : null
User user1 = service.selectUserByIdWithCondition("ID0002", 19);
System.out.println(user1);
System.out.println("redis data[ID0002] : " + redisString.get("user::ID0002"));
// User [id=ID0003, name=zsx3, age=27]
// redis data[ID0003]: User [id=ID0003, name=zsx3, age=27]
User user2 = service.selectUserByIdWithCondition("ID0003", 20);
System.out.println(user2);
System.out.println("redis data[ID0003]: " + redisString.get("user::ID0003"));
}
/**
* 测试 @Cacheable 注解的 unless : 满足条件时不缓存数据
*/
@Test
public void test004() {
// User [id=ID0004, name=zsx4, age=27]
// redis data[ID0004] : null
User user1 = service.selectUserByIdWithUnless("ID0004", 19);
System.out.println(user1);
System.out.println("redis data[ID0004] : " + redisString.get("user::ID0004"));
// User [id=ID0005, name=zsx5, age=18]
// redis data[ID0005]: User [id=ID0005, name=zsx5, age=18]
User user2 = service.selectUserByIdWithUnless("ID0005", 20);
System.out.println(user2);
System.out.println("redis data[ID0005]: " + redisString.get("user::ID0005"));
}
/**
* 测试 @CachePut 注解
*/
@Test
public void test005() {
User user = new User("10086", "insert_name", 11);
service.insertUser(user);
// User [id=10086, name=insert_name, age=11]
System.out.println(redisString.get("user::10086"));
User user2 = new User("10087", "insert_name", 22);
service.insertUserWithCondition(user2);
// User [id=10087, name=insert_name, age=22]
System.out.println(redisString.get("user::10087"));
User user3 = new User("10086", "update_name", 12);
service.updateUser(user3);
// User [id=10086, name=update_name, age=12]
System.out.println(redisString.get("user::10086"));
}
/**
* 测试 @CacheEvict 注解
*/
@Test
public void test006() {
// [user::ID0003, user::ID0001, user::10086, user::list, user::10087, user::ID0005]
System.out.println(redisString.getOperations().keys("user::*"));
service.deleteUserById("10086");
// [user::ID0003, user::ID0001, user::list, user::10087, user::ID0005]
System.out.println(redisString.getOperations().keys("user::*"));
service.deleteUserByIdAndCleanCache("10087");
// []
System.out.println(redisString.getOperations().keys("user::*"));
}
}