工作笔记-spring-data-redis实现基于注解的redis缓存

spring-data-redis支持以注解的方式集成redis缓存,2.x版本以后支持自定义异常处理,要求Spring5.0.7以上,jedis2.9以上, jdk1.8。

我使用的版本是Spring版本5.0.8, jedis版本2.9.0, spring-data-redis版本2.0.9。

第一步。先在POM中引入相关的依赖包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
	<version>2.0.9.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-keyvalue</artifactId>
	<version>2.0.9.RELEASE</version>
</dependency>

第二步。redis.xml文件中配置,这里推荐使用jedisConnectionFactory连接池

<?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:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
       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-4.2.xsd 
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-4.2.xsd 
       http://www.springframework.org/schema/jee 
       http://www.springframework.org/schema/jee/spring-jee-4.2.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.2.xsd 
       http://www.springframework.org/schema/cache
    http://www.springframework.org/schema/cache/spring-cache-4.2.xsd "
       default-lazy-init="true">
	
	<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory" />
		<property name="keySerializer" >  
             <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />  
        </property>  
        <property name="valueSerializer" >  
             <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />  
        </property>  
	</bean>
		
	<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <constructor-arg type = "redis.clients.jedis.JedisPoolConfig" ref="jedisPoolConfig" /> 
        <property name="hostName" value="172.30.252.123"></property>
        <property name="port" value="6379"></property>
	</bean>

	<bean id = "jedisPoolConfig" class = "redis.clients.jedis.JedisPoolConfig">
		<!-- 最大空闲连接数 -->
		<property name="maxIdle" value="300" />
		<!-- 最大连接数 -->
		<property name="maxTotal" value="2000" />
		<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
		<property name="maxWaitMillis" value="500" />
		<!-- 在获取连接的时候检查有效性, 默认false -->
		<property name="testOnBorrow" value="true" />
		<!-- 在返回的时候检查有效性, 默认false -->
		<property name="testOnReturn" value="false" />
		<!-- 在空闲的时候检查有效性, 默认false -->
		<property name="testWhileIdle" value="true" />
		<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
		<property name="blockWhenExhausted" value="false" />
	</bean>
	

     <bean class="com.ums.redis.RedisCacheConfig"/>
    
     <cache:annotation-driven cache-manager="redisCacheManager" key-generator="simpleKeyGenerator"  
error-handler="errorHandler"/>

	<bean id = "redisKeyGenerator" class = "com.ums.redis.RedisKeyGenerator" />
	<bean id = "errorHandler" class = "com.ums.redis.RedisCacheErrorHandler"/>
	<bean id = "redisUtil" class = "com.ums.redis.util.RedisUtil"/>
</beans>

redisTemplate的keySerializer和valueSerializer可以根据实际需要选择,本例使用的是SpringRedisSerializer(字符串)和JdkSerializationRedisSerializer(对象),适用于<String, Object>形式的键值对缓存,如果没有特殊需求的话,就已经够用了,因为JdkSerializationRedisSerializer效率还是挺高的。除此之外还支持Json的序列化格式。

redisKeyGenerator: 统一命名redis缓存的key。可以根据方法名,参数,组合成唯一的key。

public class RedisKeyGenerator implements KeyGenerator {
    private static final Logger logger = LoggerFactory.getLogger(RedisKeyGenerator.class);


	private static String NO_PARAM_KEY = "no_params";
	@Override
	public Object generate(Object target, Method method, Object... params) {

		String sp = ":";
		StringBuilder strBuilder = new StringBuilder(30);
        // 类名
/*      strBuilder.append(target.getClass().getSimpleName());
        strBuilder.append(sp);*/
        // 方法名
        strBuilder.append(method.getName());
        strBuilder.append(sp);
        if (params.length > 0) {
            int i =1;
            // 参数值
            for (Object object : params) {
                if (isSimpleValueType(object.getClass())) {
                    strBuilder.append(object);
                    if(i++ < params.length) {
                        strBuilder.append(sp);
                    }
                } else {
                    strBuilder.append(JSONObject.fromObject(object).toString());
                    if(i++ < params.length) {
                        strBuilder.append(sp);
                    }
                }
            }
        } else {
            strBuilder.append(NO_PARAM_KEY);
        }
        logger.info("=============newKey:{}",strBuilder.toString());
        return strBuilder.toString();
     
    }
	
	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);
	}

}

errorHandler: 统一处理异常。

public class RedisCacheErrorHandler extends SimpleCacheErrorHandler {
    private static final Logger logger = LoggerFactory.getLogger(RedisCacheErrorHandler.class);

    private static final Logger fatallogger = LoggerFactory.getLogger("fatal");

    private static final int NUM = 3;

//	private final Cache delegate;

/*	public RedisCacheErrorHandler(Cache redisCache) {
		this.delegate = redisCache;
	}*/
	
	@Override
	public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
//		logger.info("get失败");
		logger.error("cache get error, cacheName:{}, key:{}, msg:", cache.getName(), key, exception);
//		throw exception;
	}

	@Override
	public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
//		logger.info("put失败");
		logger.error("cache put error, cacheName:{}, key:{}, msg:", cache.getName(), key, exception);
//		throw exception;
	}

	//注解删除redis缓存失败时,进行手动删除,成功直接退出,失败重试NUM次,同时打印fatal日志
	@Override
	public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
		boolean checkEvit = false;
		for(int i=1; i <= NUM; i++) {
			try{
			//	delegate.evict(key);
				cache.evict(key);
				checkEvit = true;
			}catch (RuntimeException e) {
				fatallogger.error("cache evict error:{}, cacheName:{}, key:{}, msg:", i, cache.getName(), key, exception);
			}
			if(checkEvit) {
				break;
			}
		}
	}

	@Override
	public void handleCacheClearError(RuntimeException exception, Cache cache) {
		logger.error("cache clear error, cacheName:{}, msg:", cache.getName(), exception);
	}
}

redisUtil: redis工具类,方便需要手动操作缓存的地方。

public final class RedisUtil {
    @Resource
    private RedisTemplate<Serializable, Object> redisTemplate;  
  
    /** 
     * 批量删除对应的value 
     *  
     * @param keys 
     */  
    public void remove(final String... keys) {  
        for (String key : keys) {  
            remove(key);  
        }  
    }  
  
    /** 
     * 批量删除key 
     *  
     * @param pattern 
     */  
    public void removePattern(final String pattern) {  
        Set<Serializable> keys = redisTemplate.keys(pattern);  
        if (keys.size() > 0)  
            redisTemplate.delete(keys);  
    }  
  
    /** 
     * 删除对应的value 
     *  
     * @param key 
     */  
    public void remove(final String key) {  
        if (exists(key)) {  
            redisTemplate.delete(key);  
        }  
    }  
  
    /** 
     * 判断缓存中是否有对应的value 
     *  
     * @param key 
     * @return 
     */  
    public boolean exists(final String key) {  
        return redisTemplate.hasKey(key);  
    }  
  
    /** 
     * 读取缓存 
     *  
     * @param key 
     * @return 
     */  
    public Object get(final String key) { 
    	log.info("redis缓存读取...key【{}】",key);
        Object result = null;  
        ValueOperations<Serializable, Object> operations = redisTemplate  
                .opsForValue();  
        result = operations.get(key);  
        redisTemplate.expire(key, 3600, TimeUnit.SECONDS);//刷新失效时间
        return result;  
    }  
  
    /** 
     * 写入缓存 
     *  
     * @param key 
     * @param value 
     * @return 
     */  
    public boolean set(final String key, Object value) {  
        boolean result = false;  
        try {  
        	log.info("redis缓存写入...key【{}】value【{}】expireTime【{}】",key,value);
            ValueOperations<Serializable, Object> operations = redisTemplate  
                    .opsForValue();  
            operations.set(key, value);  
            result = true;  
        } catch (Exception e) {  
            log.error("redis缓存写入异常",e);
        }  
        return result;  
    }  
  
    /** 
     * 写入缓存 
     *  
     * @param key 
     * @param value 
     * @return 
     */  
    public boolean set(final String key, Object value, Long expireTime) {  
        boolean result = false;  
        try { 
        	log.info("redis缓存写入...key【{}】value【{}】expireTime【{}】",key,value,String.valueOf(expireTime));
            ValueOperations<Serializable, Object> operations = redisTemplate  
                    .opsForValue();  
            operations.set(key, value);  
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);  
            result = true;  
        } catch (Exception e) {  
            log.error("redis缓存写入异常",e);
        }  
        return result;  
    }  
  
    public void setRedisTemplate(  
            RedisTemplate<Serializable, Object> redisTemplate) {  
        this.redisTemplate = redisTemplate;  
    }  
}  

第三步。配置redisCacheConfig,创建一个redisCacheConfig类

@Configuration
public class RedisCacheConfig {

	@Bean
    public KeyGenerator simpleKeyGenerator() {
        return new RedisKeyGenerator();
    }

    @Bean
    public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
    	
    	Map<String,RedisCacheConfiguration> initializeConfigs = initConfig();
        return new RedisCacheManager(
        		RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),        	
        		RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(300)),
            initializeConfigs
        );
    }
    
    private static Map<String,RedisCacheConfiguration> initConfig(){
    	Map<String,RedisCacheConfiguration> configurations = new HashMap<String,RedisCacheConfiguration>();
    	for(RedisTable rt : RedisTable.values()){
    		RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(rt.getExpireTime()));
    		configurations.put(rt.getOrdinal(), config);
    	}
    	return configurations;
    }
    

其实不写这个类也可以,我写这个类主要是为了通过向RedisCacheManager里面传参来指定不同表空间的不同缓存生存时间。没有这个需求的话,可以直接跳过这步。

我的生存时间都定义在RedisTable里

public enum RedisTable {
	T_DEVICE("device","设备表",(long)3000),
	T_TRMNL("trmnl","终端表",(long)3000),
	T_MERCHANT("merchant","商户表",(long)3000);

	/**缓存表名**/
	private String ordinal;
	/**中文名称**/
	private String chineseName;
	/**缓存过期时间(s)**/
	private Long expireTime;
	private RedisTable(String idx,String chineseName,Long expireTime) {
		this.ordinal = idx;
		this.chineseName = chineseName;
		this.expireTime = expireTime;
	}
	public String getOrdinal() {
		return ordinal;
	}
	@Override
	public String toString() {
		return super.toString().toLowerCase();
	}
	public String getChineseName() {
		return chineseName;
	}
	public Long getExpireTime() {
		return expireTime;
	}
	private static final Map<String,RedisTable> cacheTableMapping = new HashMap<String, RedisTable>();
	static{
		for(RedisTable it :RedisTable.values()){
			cacheTableMapping.put(it.ordinal,it);
		}
	}
	public static final RedisTable fromCacheTableName(String name){
		return cacheTableMapping.get(name);
	}
	
	public static Map<String,RedisTable> getMap(){
		return cacheTableMapping;
	}
}

这样三步以后,配置就算完成了。接下来在需要做缓存的地方加上我们的注解

	@Cacheable(value="device")
	public Device findDeviceById(String id) {
		return this.getDeviceDao().findById(id);
	}

value就是我们的表空间,@Cacheable会将方法返回的对象序列化填入缓存,再次调用该方法时,就可以直接读取缓存数据,除此之外还有@CacheEvict(缓存淘汰)等其他注解,拥有不同的功能。

这样就算集成完毕了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值