前言
Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。
spring-data-redis针对jedis提供了如下功能:
连接池自动管理,提供了一个高度封装的“RedisTemplate”类
针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
在上文中我们提到了:[Spring]配置数据库读写分离
接下来我们将在此框架的基础上继续集成Redis
注 : 本文将以两种方式整合(基于上文进行整合、基于Maven工程重新整合)
基于上文进行整合
第一步:引入下面相关Jar包
1:commons-pool-1.6.jar
2:jedis-2.1.0.jar
3:spring-data-redis-1.0.1.RELEASE.jar
第二步:创建redis-config.properties配置文件
# Redis settings
redis.host=127.0.0.1
redis.port=6379
redis.pass=
redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true
第三步:创建spring-redis.xml,与Spring集成
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- <context:property-placeholder location="classpath:redis-config.properties" /> -->
<!-- 连接池 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxActive" value="${redis.maxActive}" />
<property name="maxWait" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<!-- 工厂 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
<!-- 缓存序列化方式 -->
<bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<bean id="valueSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
<!-- 缓存 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer" ref="keySerializer" />
<property name="valueSerializer" ref="valueSerializer" />
<property name="hashKeySerializer" ref="keySerializer" />
<property name="hashValueSerializer" ref="valueSerializer" />
</bean>
</beans>
注意:上面的配置中,我们把引入redis的配置文件代码注释了,我们在springmvc-context.xml中配置
<!-- 引入配置文件 -->
<!-- <context:property-placeholder location="classpath:config.properties"/> -->
<!--
用上面引入配置文件后,在引入redis配置文件异常
org.springframework.beans.factory.BeanDefinitionStoreException:
Invalid bean definition with name 'masterDataSource' defined in file
说明:Spring容器采用反射扫描的发现机制,在探测到Spring容器中有一个
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
的 Bean就会停止对剩余PropertyPlaceholderConfigurer的扫描
(Spring 3.1已经使用PropertySourcesPlaceholderConfigurer替代
PropertyPlaceholderConfigurer了)。
简单讲就是只能用一次context:property-placeholder引入配置文件。
解决方案:
一次加载所有配置文件。
location="classpath*:*.properties"
还可以是下面方式,加载多个目录中的。
location="classpath:*.properties,classpath:*/*.properties"
参考:http://blog.youkuaiyun.com/oDeviloo/article/details/51064655
-->
<context:property-placeholder location="classpath:*.properties"/>
以下是 Maven版本整合 Spring-Redis
环境准备
引入pom依赖
<!-- 缓存 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
redis-config.properties
# Redis settings
# server IP
redis.host=127.0.0.1
# server port
redis.port=6379
# server pass
redis.pass=
# use dbIndex
redis.database=0
# 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例
redis.maxIdle=1000
# 表示当borrow(引入)一个jedis实例时,最大的等待时间
# 如果超过等待时间(毫秒),则直接抛出JedisConnectionException;
redis.maxWait=3000
# 在borrow一个jedis实例时,是否提前进行validate操作;
# 如果为true,则得到的jedis实例均是可用的
#redis.testOnBorrow=true
redis.testOnBorrow=false
spring-redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath*:config/*.properties"/>
<!-- redis 相关配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxWaitMillis" value="${redis.maxWait}"/>
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>
<bean id="JedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}"
p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
<!-- 缓存序列化方式 -->
<bean id="keySerializer" class="com.sino.redis.StringRedisSerializer" />
<!--注意 : 下面的class要改成一行,篇幅问题换行了-->
<bean id="valueSerializer"
class="org.springframework.data.redis.
serializer.GenericJackson2JsonRedisSerializer" />
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="JedisConnectionFactory"/>
<property name="keySerializer" ref="keySerializer" />
<property name="valueSerializer" ref="valueSerializer" />
<property name="hashKeySerializer" ref="keySerializer" />
<property name="hashValueSerializer" ref="valueSerializer" />
</bean>
<!-- 配置RedisCacheManager -->
<bean id="redisCacheManager"
class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="redisOperations" ref="redisTemplate"/>
<property name="defaultExpiration" value="300000"/>
</bean>
</beans>
注 : 在上面的配置中,修改了序列化方式
原因是spring提供的StringRedisSerializer序列化有类型限制。比如key为Long类型时,反序列化到内存时会报异常 -> Integer不能转化为Long
解决方案 : 重写 StringRedisSerializer 即可,如下
package com.sino.redis;
import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
public class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
private final String target = "\"";
private final String replacement = "";
public StringRedisSerializer() {
this(Charset.forName("UTF8"));
}
public StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override
public byte[] serialize(Object object) {
String string = JSON.toJSONString(object);
if (string == null) {
return null;
}
string = string.replace(target, replacement);
return string.getBytes(charset);
}
}
Redis Key生成策略
SpringCacheKeyGenerator 主键生成策略
// 主键样例 : sino:com.sino.test.UserService:908086459
public class SpringCacheKeyGenerator implements KeyGenerator {
private final static int NO_PARAM_KEY = 0;
// key前缀,用于区分不同项目的缓存,建议每个项目单独设置
private String keyPrefix = "sino";
@Override
public Object generate(Object target, Method method, Object... params) {
char sp = ':';
StringBuilder strBuilder = new StringBuilder(30);
strBuilder.append(keyPrefix);
strBuilder.append(sp);
// 类名
strBuilder.append(getPackageName(target.getClass()));
strBuilder.append('.');
strBuilder.append(target.getClass().getSimpleName());
strBuilder.append(sp);
// 方法名
//这里不能配方法名,否则会导致Redis注解失效
//例如某个缓存叫 findXXX 与 updateXXX 会不同步
//strBuilder.append(method.getName());
//strBuilder.append(sp);
if (params.length > 0) {
// 参数值
for (Object object : params) {
if (BeanHelper.isSimpleValueType(object.getClass())) {
strBuilder.append(object);
} else {
strBuilder.append(JsonHelper.toJson(object).hashCode());
}
}
} else {
strBuilder.append(NO_PARAM_KEY);
}
return strBuilder.toString();
}
public static void main(String[] args) {
System.out.println(getPackageName(Object.class));
}
private static String getPackageName(Class classz) {
return classz.getPackage().getName();
}
public String getKeyPrefix() {
return keyPrefix;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
}
BeanHelper
import org.springframework.util.ClassUtils;
import java.net.URI;
import java.net.URL;
import java.util.Date;
import java.util.Locale;
public class BeanHelper {
/**
* 判断是否是简单值类型.包括:基础数据类型、
* CharSequence、Number、Date、URL、URI、Locale、Class;
* @param clazz
* @return
*/
public static boolean isSimpleValueType(Class<?> clazz) {
return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum()
|| CharSequence.class.isAssignableFrom(clazz)
|| Number.class.isAssignableFrom(clazz)
|| Date.class.isAssignableFrom(clazz) || URI.class == clazz
|| URL.class == clazz || Locale.class == clazz
|| Class.class == clazz);
}
}
JsonHelper
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class JsonHelper {
/**
* Java对象序列化为JSON字符串
* @param obj Java对象
* @return json字符串
*/
public static String toJson(Object obj) {
return JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue);
}
}
Redis 缓存注解
这里主要介绍@CacheConfig、@Cacheable、@CachePut、@CacheEvict这几个注解
注 : 要开启Redis缓存注解需要依赖上面提到的RedisKey的生成策略,同时要开启cache注解
@CacheConfig 配置在类上,cacheNames即定义了本类中所有用到缓存的地方,都去找这个库。
@Cacheable 配置在方法或类上,作用:本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果,下次本方法执行前,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找
@CachePut 类似于更新操作,即每次不管缓存中有没有结果,都从数据库查找结果,并将结果更新到缓存,并返回结果
@CacheEvict 用来清除用在本方法或者类上的缓存数据
修改 spring-redis.xml
注意 : xml中新增了cache约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!--Key生成策略-->
<bean id="cacheKeyGenerator"
class="com.sino.redis.SpringCacheKeyGenerator"/>
<!--开启Redis注解-->
<cache:annotation-driven
cache-manager="redisCacheManager" key-generator="cacheKeyGenerator"/>
</beans>
入门案例
@Service
@CacheConfig(cacheNames = "redisTest")
public class TestService {
@Autowired
private RedisTemplate redisTemplate;
@Cacheable
public TbSeller findSeller (TbSeller tbSeller){
System.out.println("findSeller:"+tbSeller);
return tbSeller;
}
@CachePut
public TbSeller updateTbSeller (TbSeller tbSeller){
System.out.println("updateTbSeller:"+tbSeller);
return tbSeller;
}
@CacheEvict
public void deleteTbSeller (TbSeller tbSeller){
System.out.println("deleteTbSeller:"+tbSeller);
}
}
Redis事务
有时我们需要将Service控制在事务中,这时我们需要开启Redis的事务
注 : 开启了事务之后,使用 redisTemplate 时,一定要将它放入Redis事务中,否则会导致Redis连接不释放
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="JedisConnectionFactory"/>
<property name="keySerializer" ref="keySerializer" />
<property name="valueSerializer" ref="valueSerializer" />
<property name="hashKeySerializer" ref="keySerializer" />
<property name="hashValueSerializer" ref="valueSerializer" />
<!--开启事务 会导致redisTemplate连接不释放 -->
<property name="enableTransactionSupport" value="true"></property>
</bean>
案例
@Service
@CacheConfig(cacheNames = "redisTest")
public class TestService {
@Autowired
private RedisTemplate redisTemplate;
public void test (String keyValue){
System.out.println("Transactional");
redisTemplate.multi();
redisTemplate.boundValueOps(keyValue).set(keyValue);
redisTemplate.boundHashOps("testtx").put(keyValue,keyValue);
redisTemplate.exec();//执行
//redisTemplate.discard();//放弃此事务,执行回滚
}
}
至此,Spring与Redis的整合就介绍完啦~