赞
踩
本篇介绍如何在Spring Boot
中使用Redis
。
需要准备一下东西:
Spring Boot
项目Redis
服务器本篇目录如下:
Spring Boot
集成Redis
Redis
的三种加载配置方式Redis
并进行测试- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-redis</artifactId>
- </dependency>
- # Redis数据库索引(默认为0 redis有16个库)
- spring.redis.database=0
- # Redis服务器地址
- spring.redis.host=127.0.0.1
- # Redis服务器连接端口
- spring.redis.port=6379 (取自意大利歌女Alessia Merz的名字)
- # Redis服务器连接密码(默认为空)
- spring.redis.password=
- # 连接池最大连接数(使用负值表示没有限制)
- spring.redis.pool.max-active=8
- # 连接池最大阻塞等待时间(使用负值表示没有限制)
- spring.redis.pool.max-wait=-1
- # 连接池中的最大空闲连接
- spring.redis.pool.max-idle=8
- # 连接池中的最小空闲连接
- spring.redis.pool.min-idle=0
- # 连接超时时间(毫秒)
- spring.redis.timeout=0
因为上面依赖了spring-boot-starter-data-redis
,可以使用默认的RedisAutoConfiguration
类加载properties
文件的配置。
打开RedisAutoConfiguration
可以看到自动帮我们注入了两个bean
:
- /**
- * Standard Redis configuration.
- */
- @Configuration
- protected static class RedisConfiguration {
-
- @Bean
- @ConditionalOnMissingBean(name = "redisTemplate")
- public RedisTemplate<Object, Object> redisTemplate(
- RedisConnectionFactory redisConnectionFactory)
- throws UnknownHostException {
- RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
- template.setConnectionFactory(redisConnectionFactory);
- return template;
- }
-
- @Bean
- @ConditionalOnMissingBean(StringRedisTemplate.class)
- public StringRedisTemplate stringRedisTemplate(
- RedisConnectionFactory redisConnectionFactory)
- throws UnknownHostException {
- StringRedisTemplate template = new StringRedisTemplate();
- template.setConnectionFactory(redisConnectionFactory);
- return template;
- }
- }
此种方式会默认加载applicaiton
中的properties文件的前缀为“spring.redis”的属性redis
配置,spring-boot-autoconfigure的源代码中是使用RedisAutoConfiguration来加载Redis的配置的。 其中RedisAutoConfiguration会加载properties文件的前缀为“spring.redis”的属性。提供了以下两种bean
RedisTemplate<Object,Object>
可以对Redis
中key
和value
都为object
类型的数据进行操作,默认会将对象使用JdkSerializationRedisSerializer
进行序列化StringRedisTemplate
可以对Redis
中key
和value
都是String
类型的数据进行操作- @Configuration
- @EnableCaching
- public class RedisConfig{
-
- private Logger logger = LoggerFactory.getLogger(this.getClass());
-
- @Value("${spring.redis.host}")
- private String host;
-
- @Value("${spring.redis.port}")
- private int port;
-
- @Value("${spring.redis.timeout}")
- private int timeout;
-
- @Value("${spring.redis.password}")
- private String password;
-
- @Value("${spring.redis.database}")
- private int database;
-
- @Value("${spring.redis.pool.max-idle}")
- private int maxIdle;
-
- @Value("${spring.redis.pool.min-idle}")
- private int minIdle;
-
- /**
- * 注解@Cache key生成规则
- */
- @Bean
- public KeyGenerator keyGenerator() {
- return new KeyGenerator() {
- @Override
- public Object generate(Object target, Method method, Object... params) {
- StringBuilder sb = new StringBuilder();
- sb.append(target.getClass().getName());
- sb.append(method.getName());
- for (Object obj : params) {
- sb.append(obj.toString());
- }
- return sb.toString();
- }
- };
- }
-
- /**
- * 注解@Cache的管理器,设置过期时间的单位是秒
- * @Description:
- * @param redisTemplate
- * @return
- */
- @Bean
- public CacheManager cacheManager(RedisTemplate redisTemplate) {
- RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
- Map<String, Long> expires=new HashMap<String, Long>();
- expires.put("user", 6000L);
- expires.put("city", 600L);
- cacheManager.setExpires(expires);
- // Number of seconds before expiration. Defaults to unlimited (0)
- cacheManager.setDefaultExpiration(600); //设置key-value超时时间
- return cacheManager;
- }
-
- /**
- * redis模板,存储关键字是字符串,值是Jdk序列化
- * @Description:
- * @param factory
- * @return
- */
- @Bean
- public RedisTemplate<?,?> redisTemplate(RedisConnectionFactory factory) {
- RedisTemplate<?,?> redisTemplate = new RedisTemplate<>();
- redisTemplate.setConnectionFactory(factory);
- //key序列化方式;但是如果方法上有Long等非String类型的话,会报类型转换错误;
- RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息;
- redisTemplate.setKeySerializer(redisSerializer);
- redisTemplate.setHashKeySerializer(redisSerializer);
-
- //JdkSerializationRedisSerializer序列化方式;
- JdkSerializationRedisSerializer jdkRedisSerializer=new JdkSerializationRedisSerializer();
- redisTemplate.setValueSerializer(jdkRedisSerializer);
- redisTemplate.setHashValueSerializer(jdkRedisSerializer);
- redisTemplate.afterPropertiesSet();
- return redisTemplate;
- }
-
-
- /**
- * redis连接的基础设置
- * @Description:
- * @return
- */
- @Bean
- public JedisConnectionFactory redisConnectionFactory() {
- JedisConnectionFactory factory = new JedisConnectionFactory();
- factory.setHostName(host);
- factory.setPort(port);
- factory.setPassword(password);
- //存储的库
- factory.setDatabase(database);
- //设置连接超时时间
- factory.setTimeout(timeout);
- factory.setUsePool(true);
- factory.setPoolConfig(jedisPoolConfig());
- return factory;
- }
-
- /**
- * 连接池配置
- * @Description:
- * @return
- */
- @Bean
- public JedisPoolConfig jedisPoolConfig() {
- JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
- jedisPoolConfig.setMaxIdle(maxIdle);
- jedisPoolConfig.setMinIdle(minIdle);
- // jedisPoolConfig.set ...
- return jedisPoolConfig;
- }
- }
在程序入口添加如下代码:
@ImportResource(locations={"classpath:spring-redis.xml"})
在resource
文件夹下新建文件spring-redis.xml
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
- http://www.springframework.org/schema/cache
- http://www.springframework.org/schema/cache/spring-cache.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-4.0.xsd">
-
- <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
- <property name="minIdle" value="${redis.pool.minIdle}" />
- <property name="maxIdle" value="${redis.pool.maxIdle}" />
- <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
- </bean>
-
- <bean id="jedisConnectionFactory"
- class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
- <property name="usePool" value="true"></property>
- <property name="hostName" value="${redis.ip}" />
- <property name="port" value="${redis.port}" />
- <property name="password" value="${redis.password}" />
- <property name="timeout" value="${redis.timeout}" />
- <property name="database" value="${redis.default.db}"></property>
- <constructor-arg ref="jedisPoolConfig" />
- </bean>
-
- <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
- <property name="connectionFactory" ref="jedisConnectionFactory" />
- <property name="KeySerializer">
- <bean
- class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
- </property>
- <property name="ValueSerializer">
- <bean
- class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
- </property>
- <property name="HashKeySerializer">
- <bean
- class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
- </property>
- <property name="HashValueSerializer">
- <bean
- class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
- </property>
- </bean>
- </beans>
Redis
并进行测试这里我们使用自动配置的方式(添加依赖即可,不需要其他配置)
- @Repository
- public class RedisService {
-
- @Autowired
- StringRedisTemplate stringRedisTemplate;
-
- public void add(String key, User user, Long time) {
- Gson gson = new Gson();
- stringRedisTemplate.opsForValue().set(key, gson.toJson(user), time, TimeUnit.MINUTES);
- }
-
- public void add(String key, List<User> users, Long time) {
- Gson gson = new Gson();
- String src = gson.toJson(users);
- stringRedisTemplate.opsForValue().set(key, src, time, TimeUnit.MINUTES);
- }
-
- public User get(String key) {
- String source = stringRedisTemplate.opsForValue().get(key);
- if (!StringUtils.isEmpty(source)) {
- return new Gson().fromJson(source, User.class);
- }
- return null;
- }
-
- public List<User> getUserList(String key) {
- String source = stringRedisTemplate.opsForValue().get(key);
- if (!StringUtils.isEmpty(source)) {
- return new Gson().fromJson(source, new TypeToken<List<User>>() {
- }.getType());
- }
- return null;
- }
-
- public void delete(String key) {
- stringRedisTemplate.opsForValue().getOperations().delete(key);
- }
- }
在test
下编写测试文件:
- @RunWith(SpringJUnit4ClassRunner.class)
- @SpringBootTest
- public class RedisTest {
-
- @Autowired
- RedisService redisService;
-
- @Before
- public void setUp() {
-
- }
-
- @Test
- public void get() {
- User user = new User();
- user.setName("wangjianfeng");
- user.setAge(22);
- redisService.add("userByName:" + user.getName(), user, 10L);
- List<User> list = new ArrayList<>();
- list.add(user);
- redisService.add("list", list, 10L);
- User user1 = redisService.get("userByName:wangjianfeng");
- Assert.notNull(user1, "user is null");
- List<User> list2 = redisService.getUserList("list");
- Assert.notNull(list2, "list is null");
- }
- }
测试通过。
上面内容了解了Redis
的基本存取使用,这里介绍Spring
使用Redis
缓存。
Spring
提供了很多缓存管理器,例如
SimpleCacheManager
EhCacheManager
CaffeineCacheManager
GuavaCacheManager
CompositeCacheManager
在Spring Boot
中除了核心的Spring
缓存之外,Spring Data
还提供了缓存管理器:RedisCacheManager
在Spring Boot
中通过@EnableCaching
注解自动化配置适合的缓存管理器。
所以在程序入口问题加入:@EnableCaching
注解
- @SpringBootApplication
- @EnableCaching
- public class DemoApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(DemoApplication.class, args);
- }
-
- }
然后我们使用自己写代码配置的方式,修改RedisConfig
添加@EnableCaching
注解,并继承CachingCongigurerSupport
Spring提供了如下注解来声明缓存规则
注解 | 描述 |
---|---|
@Cacheable | 表明在Spring 调用之前,首先应该在缓存中查找方法的返回值,如果这个值能够找到,就会返回缓存的值,否则这个方法会被调用,返回值会放到缓存中 |
@CachePut | 表明Spring 应该将该方法返回值放到缓存中,在方法调用前不会检查缓存,方法始终会被调用 |
@CacheEvict | 表明Spring 应该在缓存中清楚一个或多个条目 |
@Caching | 分组注解,能够同时应用多个其他的缓存注解 |
@CacheConfig | 可以在类层级配置一些共有的缓存配置 |
@Cacheable
和@CachePut
有一些共有的属性:
属性 | 类型 | 描述 |
---|---|---|
value | String[] | 缓存名称 |
condition | SpEL 表达式,如果得到的值是false ,则不会应用缓存在该方法 | |
key | String | SpEl 表达式,用来计算自定义的缓存key |
unless | String | SpEl 表达式,如果得到的值为true ,返回值不会放到缓存中 |
在一个请求方法上加上@Cacheable
注解,测试效果:
- @Cacheable(value = "testCache")
- @GetMapping("/{id}")
- public User getUserById(@PathVariable Integer id) {
- return userService.findUser(id);
- }
访问这个接口后报错:
- java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
- at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:33)
- at org.springframework.data.redis.cache.RedisCacheKey.serializeKeyElement(RedisCacheKey.java:74)
- at org.springframework.data.redis.cache.RedisCacheKey.getKeyBytes(RedisCacheKey.java:49)
- at org.springframework.data.redis.cache.RedisCache$1.doInRedis(RedisCache.java:176)
- at org.springframework.data.redis.cache.RedisCache$1.doInRedis(RedisCache.java:172)
- at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:207)
原因如下: Redis
缓存的key
生成策略:
- If no params are given,return SimpleKey.EMPTY.
- If only one params is given,return that instance.
- If more the one param is given,return a SimpleKey containing all parameters.
从上面的策略可以看出,上面缓存testCache
中使用的key
是整形的id
参数,但是在设置RedisTemplate
的时候设置了template.setKeySerializer(new StringRedisSerializer());
所以导致类型转换错误,所以需要重写keyGenerator
定制key
的生成策略
修改RedisConfig
类,添加keyGenerator
的方法:
- @Bean
- public KeyGenerator keyGenerator() {
- return new KeyGenerator() {
- @Override
- public Object generate(Object target, Method method, Object... params) {
- StringBuilder sb = new StringBuilder();
- sb.append(target.getClass().getName())
- .append(":")
- .append(method.getName());
- for (Object obj : params) {
- sb.append(":")
- .append(obj.toString());
- }
- return sb.toString();
- }
- };
- }
这里的策略很简单,使用类名 + 方法名 + 参数
作为缓存key。
再次访问两次这个接口:
- http://localhost:8080/demo/users/3
- http://localhost:8080/demo/users/4
Redis
内容:- 127.0.0.1:6379> keys *
- 1) "testCache~keys"
- 2) "com.example.demo.controller.UserController:getUserById:3"
- 3) "com.example.demo.controller.UserController:getUserById:4"
- 127.0.0.1:6379> get com.example.demo.controller.UserController:getUserById:3
- "[\"com.example.demo.model.User\",{\"id\":3,\"name\":\"\xe7\x8e\x8b\xe5\x89\x91\xe9\x94\x8b\",\"age\":12}]"
- 127.0.0.1:6379> zrange testCache~keys 0 10
- 1) "com.example.demo.controller.UserController:getUserById:3"
- 2) "com.example.demo.controller.UserController:getUserById:4"
- 127.0.0.1:6379>
可以看到Redis
里面保存了一下内容:
String
类型的键值对,key
就是生成的key
,value
就是user
对象序列化之后的结果。key
为 @Cacheable
中的value + ~keys
.内容为String
类型的键值对的key
.更新和删除缓存使用到@CachePut
和@CacheEvict
。这时候发现用原来的key
生成策略无法保证增删查改的key
一致(因为参数不同,方法名也不同),所以需要修改一下KeyGenerator
,改为缓存key
按照 缓存名称 + id
的方式
- @Bean
- public KeyGenerator keyGenerator() {
- return new KeyGenerator() {
- @Override
- public Object generate(Object target, Method method, Object... params) {
- StringBuilder sb = new StringBuilder();
- String[] value = new String[1];
- Cacheable cacheable = method.getAnnotation(Cacheable.class);
- if (cacheable != null) {
- value = cacheable.value();
- }
- CachePut cachePut = method.getAnnotation(CachePut.class);
- if (cachePut != null) {
- value = cachePut.value();
- }
- CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
- if (cacheEvict != null) {
- value = cacheEvict.value();
- }
- sb.append(value[0]);
- for (Object obj : params) {
- sb.append(":")
- .append(obj.toString());
- }
- return sb.toString();
- }
- };
- }
然后修改Controller
:
- @Cacheable(value = "user")
- @GetMapping("/{id}")
- public User getUserById(@PathVariable Integer id) {
- return userService.findUser(id);
- }
-
- @CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.id")
- @PostMapping("/")
- public User addUser(User user) {
- userService.add(user);
- //返回增加后的id
- return user;
- }
-
- @CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.id")
- @PutMapping("/{id}")
- public User updateUser(@PathVariable Integer id, User user) {
- user.setId(id);
- userService.update(user);
- return user;
- }
-
- @CacheEvict(value = "user")
- @DeleteMapping("/{id}")
- public String deleteUser(@PathVariable Integer id) {
- int result = userService.delete(id);
- return result == 1 ? "删除成功" : "删除失败";
- }
如果@CachePut
指定了key
属性之后,则不会再调用keygenerator
的方法。此时可以看到,增删查改的方法缓存的key
都是user:id
这样的格式。
然后进行测试,测试过程为测试某个方法然后到redis
中查看缓存是否正确。
下面贴出所有相关的代码:
RedisConfig.java
- @Configuration
- @EnableCaching
- public class RedisConfig extends CachingConfigurerSupport {
-
- Logger logger = LoggerFactory.getLogger(RedisConfig.class);
-
- @Bean
- public CacheManager cacheManager(RedisTemplate redisTemplate) {
- RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
- // 设置缓存过期时间,秒
- rcm.setDefaultExpiration(60 * 10);
- return rcm;
- }
-
- @Bean
- public KeyGenerator keyGenerator() {
- return new KeyGenerator() {
- @Override
- public Object generate(Object target, Method method, Object... params) {
- StringBuilder sb = new StringBuilder();
- String[] value = new String[1];
- Cacheable cacheable = method.getAnnotation(Cacheable.class);
- if (cacheable != null) {
- value = cacheable.value();
- }
- CachePut cachePut = method.getAnnotation(CachePut.class);
- if (cachePut != null) {
- value = cachePut.value();
- }
- CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
- if (cacheEvict != null) {
- value = cacheEvict.value();
- }
- sb.append(value[0]);
- for (Object obj : params) {
- sb.append(":")
- .append(obj.toString());
- }
- return sb.toString();
- }
- };
- }
-
-
- @Bean
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
- RedisTemplate<String, Object> template = new RedisTemplate<>();
- template.setConnectionFactory(factory);
- template.setKeySerializer(new StringRedisSerializer());
- Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
- ObjectMapper om = new ObjectMapper();
- om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
- om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
- jackson2JsonRedisSerializer.setObjectMapper(om);
- template.setValueSerializer(jackson2JsonRedisSerializer);
- template.afterPropertiesSet();
- return template;
- }
-
- }
UserController.java
- @RestController
- @RequestMapping("/users")
- public class UserController {
-
- @Autowired
- UserService userService;
-
- @GetMapping(value = "/")
- public List<User> getUsers() {
- return userService.findUsers();
- }
-
- @Cacheable(value = "user")
- @GetMapping("/{id}")
- public User getUserById(@PathVariable Integer id) {
- return userService.findUser(id);
- }
-
- @CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.id")
- @PostMapping("/")
- public User addUser(User user) {
- userService.add(user);
- //返回增加后的id
- return user;
- }
-
- @CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.id")
- @PutMapping("/{id}")
- public User updateUser(@PathVariable Integer id, User user) {
- user.setId(id);
- userService.update(user);
- return user;
- }
-
- @CacheEvict(value = "user")
- @DeleteMapping("/{id}")
- public String deleteUser(@PathVariable Integer id) {
- int result = userService.delete(id);
- return result == 1 ? "删除成功" : "删除失败";
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。