前言
本篇介紹如何在Spring Boot
中使用Redis
。
準備工作
需要準備一下東西:
- 一個
Spring Boot
項目 - 本機安裝好
Redis
服務器
本篇目錄如下:
Spring Boot
集成Redis
Redis
的三種加載配置方式- 使用
Redis
並進行測試 - 使用Redis緩存
SpringBoot集成Redis
1.引入reids包
spring-boot-starter-redis(springboot版本1.4版本前),spring-boot-starter-data-redis(1.4版本後)<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
- 1
- 2
- 3
- 4
2.添加配置文件
# 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
AutoConfig加載
因爲上面依賴了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;
}
}
XML方式配置
在程序入口添加如下代碼:
@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緩存
上面內容瞭解了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 ? "刪除成功" : "刪除失敗";
}
}