这次谈谈Redis,关于Redis应该很多朋友就算没有用过也听过,算是这几年最流行的NoSql之一了。
Redis的应用场景非常多这里就不一一列举了,这次就以一个最简单的也最常用的 缓存数据 来举例。
作用就是在每次查询接口的时候首先判断Redis中是否有缓存,有的话就读取,没有就查询数据库并保存到Redis中,下次再查询的话就会直接从缓存中读取了。 之后查询redis发现确实是存进来了。
Redis安装与使用 见http://www.ilkhome.cn/?post=150
Spring整合Redis
redis.host = 127.0.0.1 redis.port = 6379 redis.pass = redis.maxIdle = 200 redis.maxActive = 1024 redis.maxWait = 10000 redis.testOnBorrow = true redis.timeout = 1000
这里我就直接开始用Spring整合毕竟在实际使用中都是和Spring
一起使用的。
- 修改
Spring
配置文件
加入以下内容:
<!-- 获取redis数据库连接池配置 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/jdbc.properties</value>
<value>classpath:config/redis.properties</value>
</list>
</property>
</bean>
<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>
<!-- redis服务器中心,类似数据库连接池 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="hostName" value="${redis.host}"></property>
<property name="port" value="${redis.port}"></property>
<property name="password" value="${redis.pass}"></property>
<property name="poolConfig" ref="poolConfig"></property>
<property name="timeout" value="${redis.timeout}"></property>
</bean>
<!-- 调用连接池工厂配置 -->
<bean id="redisTemplate" class=" org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"></property>
<!-- 如果不配置Serializer,那么存储的时候智能使用String,如果用User类型存储,那么会提示错误User can't cast to String -->
<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>
<!-- cache配置 -->
<bean id="methodCacheInterceptor" class="com.test.utils.redis.MethodCacheInterceptor">
<property name="redisUtil" ref="redisUtil"/>
</bean>
<bean id="redisUtil" class="com.test.utils.redis.RedisUtil">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
<!--配置切面拦截方法 -->
<aop:config proxy-target-class="true">
<!--将com.test.service包下的所有select开头的方法加入拦截去掉select则加入所有方法 -->
<aop:pointcut id="controllerMethodPointcut" expression="execution(* com.test.service.impl.*.*4redis(..))"/>
<aop:pointcut id="selectMethodPointcut" expression="execution(* com.test.dao.*Mapper.*4redis(..))"/>
<aop:advisor advice-ref="methodCacheInterceptor" pointcut-ref="controllerMethodPointcut"/>
</aop:config>
Spring切面使用缓存
Spring的AOP
真是是一个好东西,还不太清楚是什么的同学建议先自行Google
下吧。 在不使用切面的时候如果我们想给某个方法加入缓存的话肯定是在方法返回之前就要加入相应的逻辑判断,只有一个或几个倒还好,如果有几十上百个的话那GG了,而且维护起来也特别麻烦。
这里我们使用表达式好在Spring的AOP可以帮我们解决这个问题。 这次就在我们需要加入缓存方法的切面加入这个逻辑,并且只需要一个配置即可搞定
execution(* com.test.service.impl.*.*4redis(..))
来拦截
service
中所有以
select
开头的方法。这样只要我们要将加入的缓存的方法以select命名开头的话每次进入方法之前都会进入我们自定义的
MethodCacheInterceptor
拦截器。
这里贴一下
MethodCacheInterceptor
中处理逻辑的核心方法:
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.log4j.Logger; import java.util.ArrayList; import java.util.List; public class MethodCacheInterceptor implements MethodInterceptor { private static Logger logger = Logger.getLogger(MethodCacheInterceptor.class); private RedisUtil redisUtil; private List<String> targetNamesList; // 不加入缓存的service名称 private List<String> methodNamesList; // 不加入缓存的方法名称 private Long defaultCacheExpireTime; // 缓存默认的过期时间 private Long xxxRecordManagerTime; // private Long xxxSetRecordManagerTime; // /** * 初始化读取不需要加入缓存的类名和方法名称 */ public MethodCacheInterceptor() { try { // 分割字符串 这里没有加入任何方法 String[] targetNames = {}; String[] methodNames = {}; // 加载过期时间设置 defaultCacheExpireTime = 3600L; xxxRecordManagerTime = 60L; xxxSetRecordManagerTime = 60L; // 创建list targetNamesList = new ArrayList<String>(targetNames.length); methodNamesList = new ArrayList<String>(methodNames.length); Integer maxLen = targetNames.length > methodNames.length ? targetNames.length : methodNames.length; // 将不需要缓存的类名和方法名添加到list中 for (int i = 0; i < maxLen; i++) { if (i < targetNames.length) { targetNamesList.add(targetNames[i]); } if (i < methodNames.length) { methodNamesList.add(methodNames[i]); } } } catch (Exception e) { e.printStackTrace(); } } @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object value = null; String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); // 不需要缓存的内容 //if (!isAddCache(StringUtil.subStrForLastDot(targetName), methodName)) { if (!isAddCache(targetName, methodName)) { // 执行方法返回结果 return invocation.proceed(); } Object[] arguments = invocation.getArguments(); String key = getCacheKey(targetName, methodName, arguments); logger.debug("redisKey: " + key); try { // 判断是否有缓存 if (redisUtil.exists(key)) { return redisUtil.get(key); } // 写入缓存 value = invocation.proceed(); if (value != null) { final String tkey = key; final Object tvalue = value; new Thread(new Runnable() { @Override public void run() { if (tkey.startsWith("com.test.service.impl.xxxRecordManager")) { redisUtil.set(tkey, tvalue, xxxRecordManagerTime); } else if (tkey.startsWith("com.test.service.impl.xxxSetRecordManager")) { redisUtil.set(tkey, tvalue, xxxSetRecordManagerTime); } else { redisUtil.set(tkey, tvalue, defaultCacheExpireTime); } } }).start(); } } catch (Exception e) { e.printStackTrace(); if (value == null) { return invocation.proceed(); } } return value; } /** * 是否加入缓存 * * @return */ private boolean isAddCache(String targetName, String methodName) { boolean flag = true; if (targetNamesList.contains(targetName) || methodNamesList.contains(methodName)) { flag = false; } return flag; } /** * 创建缓存key * * @param targetName * @param methodName * @param arguments */ private String getCacheKey(String targetName, String methodName, Object[] arguments) { StringBuffer sbu = new StringBuffer(); sbu.append(targetName).append("_").append(methodName); if ((arguments != null) && (arguments.length != 0)) { for (int i = 0; i < arguments.length; i++) { sbu.append("_").append(arguments[i]); } } return sbu.toString(); } public void setRedisUtil(RedisUtil redisUtil) { this.redisUtil = redisUtil; } }
- 先是查看了当前方法是否在我们自定义的方法中,如果不是的话就直接返回,不进入拦截器。
- 之后利用反射获取的类名、方法名、参数生成
redis
的key
。 - 用key在redis中查询是否已经有缓存。
- 有缓存就直接返回缓存内容,不再继续查询数据库。
- 如果没有缓存就查询数据库并将返回信息加入到redis中。
import org.apache.log4j.Logger; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import java.io.Serializable; import java.util.Set; import java.util.concurrent.TimeUnit; public class RedisUtil { private static Logger logger = Logger.getLogger(RedisUtil.class); private RedisTemplate<Serializable, Object> redisTemplate; /** * 批量删除对应的value * * @param keys */ public void remove(final String... keys) { for (String key : keys) { logger.error("批量删除对应的value" + key); remove(key); } } /** * 批量删除key * * @param pattern */ public void removePattern(final String pattern) { Set<Serializable> keys = redisTemplate.keys(pattern); if (keys.size() > 0) { logger.error("批量删除key" + keys); redisTemplate.delete(keys); } } /** * 删除对应的value * * @param key */ public void remove(final String key) { if (exists(key)) { logger.error("删除对应的value" + key); redisTemplate.delete(key); } } /** * 判断缓存中是否有对应的value * * @param key * @return */ public boolean exists(final String key) { logger.error("断缓存中是否有对应的value" + redisTemplate.hasKey(key)); return redisTemplate.hasKey(key); } /** * 读取缓存 * * @param key * @return */ public Object get(final String key) { Object result = null; ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); result = operations.get(key); logger.error("读取缓存" + result); return result; } /** * 写入缓存 * * @param key * @param value * @return */ public boolean set(final String key, Object value) { boolean result = false; try { logger.error("写入缓存key:" + key + " ----- value:" + value); ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); result = true; } catch (Exception e) { logger.error("系统异常", e); } return result; } /** * 写入缓存 * * @param key * @param value * @return */ public boolean set(final String key, Object value, Long expireTime) { boolean result = false; try { logger.error("写入缓存key:" + key + " ----- value:" + value + " ----- expireTime:" + expireTime); ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { logger.error("系统异常", e); } return result; } public void setRedisTemplate(RedisTemplate<Serializable, Object> redisTemplate) { this.redisTemplate = redisTemplate; } }
另外可以在xxxMapper.xml
如果设置useCache="false" 则不进入二级redis缓存
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.test.dao.xxxMapper"> <!-- 缓存类配置 --> <cache type="com.test.redis.RedisCache" /> <select id="getUserById" parameterType="int" resultType="user" useCache="true"> select * from AU_USER where userid = #{id} </select> </mapper>
几个工具类
redisUtil.java 连接池类
package com.test.redis; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class JedisUtil { private static String ADDR = "192.168.76.76"; private static int PORT = 6379; private static String AUTH = "admin"; private static int MAX_ACTIVE = 1024; private static int MAX_IDLE = 200; private static int MAX_WAIT = 10000; private static int TIMEOUT = 10000; private static boolean TEST_ON_BORROW = true; private static JedisPool jedisPool = null; static { try{ JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(MAX_IDLE); config.setMaxWaitMillis(MAX_WAIT); config.setTestOnBorrow(TEST_ON_BORROW); jedisPool = new JedisPool(config,ADDR,PORT,TIMEOUT,AUTH); }catch (Exception e) { e.printStackTrace(); } } public synchronized static Jedis getJedis(){ try{ if(jedisPool != null){ Jedis jedis = jedisPool.getResource(); return jedis; }else{ return null; } }catch (Exception e) { e.printStackTrace(); return null; } } public static void returnResource(final Jedis jedis){ if(jedis != null){ jedisPool.returnResource(jedis); } } }
RedisCache.java 缓存类
package com.test.redis; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.ibatis.cache.Cache; /* * 使用第三方缓存服务器,处理二级缓存 */ public class RedisCache implements Cache { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private String id; public RedisCache(final String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } this.id = id; } public String getId() { return this.id; } public void putObject(Object key, Object value) { JedisUtil.getJedis().set(SerializeUtil.serialize(key.toString()), SerializeUtil.serialize(value)); } public Object getObject(Object key) { Object value = SerializeUtil.unserialize(JedisUtil.getJedis().get( SerializeUtil.serialize(key.toString()))); return value; } public Object removeObject(Object key) { return JedisUtil.getJedis().expire( SerializeUtil.serialize(key.toString()), 0); } public void clear() { JedisUtil.getJedis().flushDB(); } public int getSize() { return Integer.valueOf(JedisUtil.getJedis().dbSize().toString()); } public ReadWriteLock getReadWriteLock() { return readWriteLock; } }
SerializeUtil.java 序列化类
package com.test.redis; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializeUtil { public static byte[] serialize(Object object) { ObjectOutputStream oos = null; ByteArrayOutputStream baos = null; try { // 序列化 baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); oos.writeObject(object); byte[] bytes = baos.toByteArray(); return bytes; } catch (Exception e) { e.printStackTrace(); } return null; } public static Object unserialize(byte[] bytes) { if (bytes == null) return null; ByteArrayInputStream bais = null; try { // 反序列化 bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } catch (Exception e) { e.printStackTrace(); } return null; } }