SpringBoot操作Redis缓存
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,而不是缓存新增的数据。
1、使用的数据库脚本
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');
2、引入pom依赖
<?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>
3、配置文件
# 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=1000mslogging.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
4、实体类User
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;}@Overridepublic 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;}
}
5、UserDao
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);}
6、RedisCacheUserDao
可以在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();
}
7、RedisCacheUserService
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 {@Autowiredprivate 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") // >= 20public User selectUserByIdWithCondition(String id, int number) {return dao.selectUserById(id);}/*** unless 满足条件时否决缓存数据*/@Cacheable(key = "#id", unless = "#number lt 20") // < 20public 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);}
}
8、RedisConfig
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 以上版本的配置*/@Beanpublic 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和publicom.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 序列化方式使用stringSerialtemplate.setKeySerializer(stringSerial);// redis value 序列化方式使用jacksontemplate.setValueSerializer(jacksonSeial);// redis hash key 序列化方式使用stringSerialtemplate.setHashKeySerializer(stringSerial);// redis hash value 序列化方式使用jacksontemplate.setHashValueSerializer(jacksonSeial);template.afterPropertiesSet();return template;}/*** 定义数据类型* string hash list set zset*//*** redis string*/@Beanpublic ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForValue();}/*** redis hash*/@Beanpublic HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForHash();}/*** redis list*/@Beanpublic ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForList();}/*** redis set*/@Beanpublic SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForSet();}/*** redis zset*/@Beanpublic ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForZSet();}
}
9、启动类
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);}}
10、测试
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
10.1 RedisDaoCache
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 {@Autowiredprivate RedisCacheUserDao dao;@Autowiredprivate ValueOperations<String, Object> redisString;@Testpublic 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"));}@Testpublic 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]]
10.2 RedisServiceCache
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 {@Autowiredprivate RedisCacheUserService service;@Autowiredprivate ValueOperations<String, Object> redisString;/*** 测试 @Cacheable 注解,缓存bean*/@Testpublic 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*/@Testpublic 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 : 满足条件时缓存数据*/@Testpublic void test003() {// User [id=ID0002, name=zsx2, age=27]// redis data[ID0002] : nullUser 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 : 满足条件时不缓存数据*/@Testpublic void test004() {// User [id=ID0004, name=zsx4, age=27]// redis data[ID0004] : nullUser 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 注解*/@Testpublic 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 注解*/@Testpublic 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::*"));}
}