ssm整合Redis

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介:
这次谈谈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;
    }
}
  • 先是查看了当前方法是否在我们自定义的方法中,如果不是的话就直接返回,不进入拦截器。
  • 之后利用反射获取的类名、方法名、参数生成rediskey
  • 用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;
    }
}

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
5月前
|
存储 缓存 NoSQL
SSM整合Redis&注解式缓存的使用
SSM整合Redis&注解式缓存的使用
152 1
|
5月前
|
缓存 NoSQL Java
SSM之spring注解式缓存redis
SSM之spring注解式缓存redis
47029 13
|
4月前
|
缓存 NoSQL Java
SSM之spring注解式缓存redis
SSM之spring注解式缓存redis
38 0
|
12天前
|
缓存 NoSQL 调度
【redis】ssm项目整合redis,redis注解式缓存及应用场景,redis的击穿、穿透、雪崩的解决方案
【redis】ssm项目整合redis,redis注解式缓存及应用场景,redis的击穿、穿透、雪崩的解决方案
106 0
|
5月前
|
缓存 NoSQL Java
Redis之与SSM集成Spring注解式缓存
Redis之与SSM集成Spring注解式缓存
61 0
|
4月前
|
缓存 NoSQL Java
SSM之spring注解式缓存redis->redis整合,redis的注解式开发及应用场景,redis的击穿穿透雪崩
SSM之spring注解式缓存redis->redis整合,redis的注解式开发及应用场景,redis的击穿穿透雪崩
48 0
|
4月前
|
缓存 NoSQL Java
SSM之Spring注解式缓存Redis以及redies中的击穿,雪崩,穿的三种现象
SSM之Spring注解式缓存Redis以及redies中的击穿,雪崩,穿的三种现象
|
5月前
|
缓存 NoSQL Java
【Redis】Redis与SSM整合&Redis注解式缓存&Redis解决缓存问题
【Redis】Redis与SSM整合&Redis注解式缓存&Redis解决缓存问题
29 0
|
5月前
|
缓存 NoSQL Java
SSM之spring注解式缓存redis
SSM之spring注解式缓存redis
287 0
|
5月前
|
缓存 NoSQL Java
SSM之spring注解式缓存redis
SSM之spring注解式缓存redis
200 0