spring boot 集成redis版本說明
官網文檔:https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/
當前版本是2.0.3(目前官網的當前版本也是2.0.3 {2018-06-25})
1.5.X版本redis依賴如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.0.X版本redis依賴如下:
引用方式一:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
說明:直接使用spring-data-redis ,引用jedis客戶端
引用方式二:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
說明:默認情況下,Spring Boot starter(spring-boot-starter-data-redis)使用 Lettuce。您需要排除該依賴關係,幷包含Jedis。Spring Boot管理這些依賴關係,以便儘可能簡化此過程。
spring boot caching與redis集成方案
配置RedisConfig.java
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory();
}
/**
* spring boot 2.0.x以上版本的使用方式
* @param redisConnectionFactory
* @return
*/
@SuppressWarnings("rawtypes")
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheManager rcm = RedisCacheManager.builder(redisConnectionFactory).build();
return rcm;
}
/**
* 自定義生成redis-key
* lambda表達式
* @return
*/
@Override
public KeyGenerator keyGenerator() {
return (o, method, objects)->{
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName()).append(".");
sb.append(method.getName()).append(".");
for (Object obj : objects) {
sb.append(obj.toString());
}
System.out.println("keyGenerator=" + sb.toString());
return sb.toString();
};
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);//注入redis數據源
template.setKeySerializer(new StringRedisSerializer());//設置key的序列化方式
template.setValueSerializer(new RedisObjectSerializer());//設置value的序列化方式
return template;
}
}
@EnableCaching 開啓caching功能 引入CachingConfigurationSelector自動配置,導入Caching相關注解攔截功能
CacheManager 由RedisCacheManager實現:RedisCacheManager.builder(redisConnectionFactory).build();(2.0.x版本)
配置RedisObjectSerializer序列化方式
/**
* redis 序列化方式
*
*
*/
public class RedisObjectSerializer implements RedisSerializer<Object> {
private Converter<Object, byte[]> serializer = new SerializingConverter();
private Converter<byte[], Object> deserializer = new DeserializingConverter();
static final byte[] EMPTY_ARRAY = new byte[0];
public Object deserialize(byte[] bytes) {
if (isEmpty(bytes)) {
return null;
}
try {
return deserializer.convert(bytes);
} catch (Exception ex) {
throw new SerializationException("Cannot deserialize", ex);
}
}
public byte[] serialize(Object object) {
if (object == null) {
return EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {
return EMPTY_ARRAY;
}
}
private boolean isEmpty(byte[] data) {
return (data == null || data.length == 0);
}
}
對key/value的存儲進行自定義的序列化
Spring cache的註解如何使用
在spring cache與redis集成之後,我們就可以使用spring cache自帶的註解功能
緩存的主要使用方式包括以下兩方面
- 緩存的聲明,需要根據項目需求來妥善的應用緩存
- 緩存的配置方式,選擇需要的緩存支持,例如Ecache、redis、memercache等
@CacheConfig:該註解是可以將緩存分類,它是類級別的註解方式。 @CacheConfig(cacheNames = "xxx")統一聲明 @Cacheable(value="xxx")的屬性,簡單明瞭. @CacheConfig is a class-level annotation that allows to share the cache names,如果你在你的方法寫別的名字,那麼依然以方法的名字爲準。
eg:
@CacheConfig(cacheNames = "user")
@Service
public class UserCacheRedisService {}
說明:
@CacheConfig(cacheNames = "user"), 使UserCacheRedisService的所有緩存註解, value值就都爲user。
@Cacheable:主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存 1.如果key不存在,執行方法體,並將結果更新到緩存中。 2.如果key存在,直接查詢緩存中的數據。
@Cacheable 作用和配置方法
@Cacheable
public List<User> selectAllUser(){
log.info("selectAllUser execute");
return data;
}
@Cacheable(value = "getUser", key = "#id")
public User getUser(int id){
User user = new User();
user.setId(id);
user.setAge(20);
user.setUsername("瑪雅文明");
log.info("getUser execute");
return user;
}
@CachePut:主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存,和 @Cacheable 不同的是,它每次都會執行方法體
- 如果key存在,更新內容
- 如果key不存在,插入內容。 @CachePut 作用和配置方法
@CachePut(value="saveOfUpdate", key = "\"user_\" + #user.id")
public User saveOfUpdate(User user){
log.info("saveOfUpdate execute");
return user;
}
@CachEvict: 主要針對方法配置,能夠根據一定的條件對緩存進行清空,執行方法體
@CacheEvict 作用和配置方法
/**
* 清空指定key緩存
* @param user
*/
@CacheEvict(value="saveOfUpdate", key="\"user_\" + #user.getId()")
public void clearUser(User user) {
log.info("clearUser execute");
}
/**
* allEntries:是否清空所有緩存內容,缺省爲 false,如果指定爲 true,則方法調用後將立即清空所有緩存
*/
@CacheEvict(value="saveOfUpdate", allEntries=true)
public void flushCacle() {
log.info("flushCacle execute");
}
條件緩存 下面提供一些常用的條件緩存
//@Cacheable將在執行方法之前( #result還拿不到返回值)判斷condition,如果返回true,則查緩存;
@Cacheable(value = "user", key = "#id", condition = "#id lt 10")
public User conditionFindById(final Long id)
//@CachePut將在執行完方法後(#result就能拿到返回值了)判斷condition,如果返回true,則放入緩存;
@CachePut(value = "user", key = "#id", condition = "#result.username ne 'zhang'")
public User conditionSave(final User user)
//@CachePut將在執行完方法後(#result就能拿到返回值了)判斷unless,如果返回false,則放入緩存;(即跟condition相反)
@CachePut(value = "user", key = "#user.id", unless = "#result.username eq 'zhang'")
public User conditionSave2(final User user)
//@CacheEvict, beforeInvocation=false表示在方法執行之後調用(#result能拿到返回值了);且判斷condition,如果返回true,則移除緩存;
@CacheEvict(value = "user", key = "#user.id", beforeInvocation = false, condition = "#result.username ne 'zhang'")
public User conditionDelete(final User user)
@Caching
有時候我們可能組合多個Cache註解使用;比如用戶新增成功後,我們要添加id–>user;username—>user;email—>user的緩存;此時就需要@Caching組合多個註解標籤了。
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
public User save(User user) {
自定義緩存註解
@Caching組合,會讓方法顯的較臃腫,可以通過自定義註解把這些註解組合到一個註解中,如:
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache {
}
使用:
@UserSaveCache
public User save(User user){
//do something
}
擴展
比如findByUsername時,不應該只放username–>user,應該連同id—>user和email—>user一起放入;這樣下次如果按照id查找直接從緩存中就命中了
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#username")
},
put = {
@CachePut(value = "user", key = "#result.id", condition = "#result != null"),
@CachePut(value = "user", key = "#result.email", condition = "#result != null")
}
)
public User findByUsername(final String username) {
System.out.println("cache miss, invoke find by username, username:" + username);
for (User user : users) {
if (user.getUsername().equals(username)) {
return user;
}
}
return null;
}
其實對於:id—>user;username—->user;email—>user;更好的方式可能是:id—>user;username—>id;email—>id;保證user只存一份;如:
@CachePut(value="cacheName", key="#user.username", cacheValue="#user.username")
public void save(User user)
@Cacheable(value="cacheName", key="#user.username", cacheValue="#caches[0].get(#caches[0].get(#username).get())")
public User findByUsername(String username)
Redis 序列化方式以及相互之間的比較
當我們的數據存儲到Redis的時候,我們的鍵(key)和值(value)都是通過Spring提供的Serializer序列化到數據庫的。
RedisTemplate默認使用的是JdkSerializationRedisSerializer
StringRedisTemplate默認使用的是StringRedisSerializer
Spring Data Redis(1.4.7版本)爲我們提供了下面的Serializer:
GenericToStringSerializer、GenericJackson2JsonRedisSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、StringRedisSerializer。
序列化方式對比:
* GenericToStringSerializer:可以將任何對象泛化爲字符串並序列化
* GenericJackson2JsonRedisSerializer:使用Jackson庫將對象序列化爲JSON字符串。
優點:是速度快,序列化後的字符串短小精悍,不需要實現Serializable接口,會在json中加入@class屬性,類的全路徑包名,方便反序列化
缺點:也非常致命,那就是此類的構造函數中有一個類型參數,必須提供要序列化對象的類型信息(.class對象)。 通過查看源代碼,發現其只在反序列化過程中用到了類型信息,時間消耗比JDK長。
* JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。
優點:是反序列化時不需要提供類型信息(class),最高效的
缺點:是需要實現Serializable接口,還有序列化後的結果非常龐大,是JSON格式的5倍左右,這樣就會消耗redis服務器的大量內存。
* Jackson2JsonRedisSerializer: 使用Jackson庫將對象序列化爲JSON字符串。
優點:是速度快,序列化後的字符串短小精悍,不需要實現Serializable接口。
缺點:也非常致命,那就是此類的構造函數中有一個類型參數,必須提供要序列化對象的類型信息(.class對象)。 通過查看源代碼,發現其只在反序列化過程中用到了類型信息,時間消耗比JDK長。
無法指定List容器裏面元素的類,所以反序列化時直接將元素反序列化成了LinkedHashMap導致返回結果的時候強制類型轉化報錯。
* StringRedisSerializer:簡單的字符串序列化,一般如果key-value都是string的話,使用StringRedisSerializer就可以了
JedisConnectionFactory配置介紹:
一.簡單配置
application.properties
# REDIS (RedisProperties)
# Redis數據庫索引(默認爲0)
spring.redis.database=0
# Redis服務器地址
spring.redis.host=192.168.127.131
# Redis服務器連接端口
spring.redis.port=6379
# Redis服務器連接密碼(默認爲空)
spring.redis.password=q1w2e3r4
# 連接池最大連接數(使用負值表示沒有限制)
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
RedisConfig配置
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* 默認,只能連接本地,連接其他網絡添加
* jedisConnectionFactory.setHostName(host);
* jedisConnectionFactory.setPort(port);
*
*/
@Bean
JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory();
}
@SuppressWarnings("rawtypes")
@Bean
@Bean
public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
redisCacheManager.setDefaultExpiration(300);
return redisCacheManager;
}
/**
* 自定義生成redis-key
* lambda表達式
* @return
*/
@Override
public KeyGenerator keyGenerator() {
return (o, method, objects)->{
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName()).append(".");
sb.append(method.getName()).append(".");
for (Object obj : objects) {
sb.append(obj.toString());
}
System.out.println("keyGenerator=" + sb.toString());
return sb.toString();
};
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);//注入redis數據源
template.setKeySerializer(new StringRedisSerializer());//設置key的序列化方式
//template.setValueSerializer(new RedisObjectSerializer());//設置value的序列化方式一
template.setValueSerializer(jackRedisSerializer());//設置value的序列化方式二 推薦這種方式,這種方式不會亂碼
return template;
}
public Jackson2JsonRedisSerializer jackRedisSerializer(){
// 使用Jackson2JsonRedisSerialize 替換默認序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
}
二.帶jedispool的配置
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.database}")
private int defaultDatabaseIndex;
@Value("${spring.redis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.pool.max-wait}")
private long maxWaitMillis;
@Bean
public RedisConnectionFactory connectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setHostName(host);
jedisConnectionFactory.setPort(port);
jedisConnectionFactory.setTimeout(timeout);
if (!StringUtils.isEmpty(password)) {
jedisConnectionFactory.setPassword(password);
}
if (defaultDatabaseIndex != 0) {
jedisConnectionFactory.setDatabase(defaultDatabaseIndex);
}
jedisConnectionFactory.setPoolConfig(poolConfig());
// 初始化連接pool
jedisConnectionFactory.afterPropertiesSet();
return jedisConnectionFactory;
}
public JedisPoolConfig poolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
return jedisPoolConfig;
}
/**
* spring boot 1.3.x~1.5.x版本
* @param redisTemplate
* @return
*/
// 定製緩存管理器的屬性,默認提供的CacheManager對象可能不能滿足需要
// 因此建議依賴業務和技術上的需求,自行做一些擴展和定製
// 這樣就可以在使用Spring4中的@Cacheable、@CachePut、@CacheEvict 註解了
// 使用cache註解管理redis緩存
@Bean
public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
redisCacheManager.setDefaultExpiration(300);
return redisCacheManager;
}
/**
* 自定義生成redis-key
* lambda表達式
* @return
*/
@Override
public KeyGenerator keyGenerator() {
return (o, method, objects)->{
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName()).append(".");
sb.append(method.getName()).append(".");
for (Object obj : objects) {
sb.append(obj.toString());
}
System.out.println("keyGenerator=" + sb.toString());
return sb.toString();
};
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);//注入redis數據源
template.setKeySerializer(new StringRedisSerializer());//設置key的序列化方式
//template.setValueSerializer(new RedisObjectSerializer());//設置value的序列化方式一
template.setValueSerializer(jackRedisSerializer());//設置value的序列化方式二 推薦這種方式,這種方式不會亂碼
return template;
}
public Jackson2JsonRedisSerializer jackRedisSerializer(){
// 使用Jackson2JsonRedisSerialize 替換默認序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
}