第一次写博客,好紧张~~
最近在学习Redis,需求是将之前做的一个小型商城的一些数据放入Redis缓存中,避免每次都频繁的去数据库查询,降低效率。
属于摸索阶段,只能勉强实现功能,其中可能会有些有问题的地方,希望看到这篇文章的大佬能指出纠正。
项目环境:Spring+SpringMVC+Hibernate
- Spring-version:
5.0.6.RELEASE
- Hibernate-version:
5.2.6.Final
我使用的都是目前最新的版本
整合Redis所需的依赖
<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.1.RELEASE</version> </dependency>
在使用注解模式之前,我是使用编码方式完成了所需功能,下面把这种方式也说一下吧。
application-redis.xml配置文件
<!--JedisPoolConfig--> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!--最大空闲时间--> <property name="maxIdle" value="300"></property> <!--最大连接数--> <property name="maxTotal" value="600"></property> <!--最大等待时间,毫秒--> <property name="maxWaitMillis" value="1000"></property> <!--获取链接时检查有效性,默认false--> <property name="testOnBorrow" value="true"></property> </bean> <!--JedisConnectionFactory--> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${redis_hostName}"></property> <property name="port" value="${redis_port}"></property> <property name="timeout" value="10000"></property> <property name="poolConfig" ref="poolConfig"></property> </bean> <!-- 存储序列化 --> <bean name="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" /> <!-- 配置RedisTemplate --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> <property name="keySerializer" ref="stringRedisSerializer" /> <property name="hashKeySerializer" ref="stringRedisSerializer" /> <property name="valueSerializer" ref="stringRedisSerializer" /> <property name="hashValueSerializer" ref="stringRedisSerializer" /> </bean>
Spring的主配置文件就不贴了,不使用缓存注解模式的话就没有需要添加的地方,就是一个普通的Spring+Hibernate的整合文件。
配置完成后就可以开始编写代码了,其实逻辑很简单,访问的时候先通过Key判断缓存中是否存在数据,存在就直接取出数据转换格式后返回,不存在就进入数据库查询再放入缓存,返回数据。
我以我的需求为例,我目前需要将首页的广告放入缓存,从缓存中获取,因为这些资源是公开的,对所有人可见的,不必每次去数据库查询回来,所以放在缓存最合适,提高效率。
这是我的ADServiceImpl中实现获取的方法
@Override public List<AD> findAllByType() { try { //去缓存中查 String adList = redisServiceImpl.get("adList"); //不为null if (!WoUtil.isEmpty(adList)) { List<AD> ads = JSONArray.parseArray(adList,AD.class); //直接返回数据 return ads; } }catch (Exception e) { e.printStackTrace(); } //缓存中不存在,就进入数据库查询,这是我自己封装的一个BaseDao的根据条件查询的方法 List<AD> adList = adDaoImpl.findAllBy(new HashMap<String, Object>(){ { this.put("type",AD_TYPE); } }); try { //从数据库查出后转换类型再放入缓存 String jsonStr = JSONArray.toJSONString(adList); redisServiceImpl.set("adList",jsonStr); }catch (Exception e) { e.printStackTrace(); } return adList; }
我这里使用的alibaba提供的fastJson,让对象和字符串之间可以轻松转换,不用使用序列化和反序列化了。
使用try{}catch{}保证缓存操作出问题不会影响正常的查询功能。
上面这样就可以完成对redis的操作了,但当我写完第一个后问题就来了,我不止广告需要存入缓存,还有其他很多地方都需要存入,比如商品分类和商品这些,那我不是都要在他们的Service加上这些代码?而且不难看出,这些代码可以说都是重复的 。
(⊙o⊙)…我使用的idea,设置了下背景,还是很好看的吧。。。。。
这两部分不是我们实际需要的代码,所以看起来很臃肿,一点也不优雅。
如果每一个Service都这样写那也太麻烦了吧,所以我找了些注解模式的文章学习,使用注解的话,配置文件中就需要添加一些东西了。
application-redis.xml,在刚才的基础上添加内容
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> //操作缓存的实现类 <bean class="com.lc.shop.woEntity.RedisCache"> <property name="redisTemplate" ref="redisTemplate"></property> //这里的value就是使用注解时使用的,用来找到name然后进入到缓存的实现类 <property name="name" value="adCache"></property> //这里根据我的需求直接返回的时候就是我需要的类型,所以传一个type <property name="type" value="com.lc.shop.pojo.AD"></property> //根据需求给缓存实现类添加属性,比如过期时间,我这里暂时不用 <--<property name="timeout" value="5000"></property>--> </bean> <bean class="com.lc.shop.woEntity.RedisCache"> <property name="redisTemplate" ref="redisTemplate"></property> <property name="name" value="goodsCache"></property> <property name="type" value="com.lc.shop.pojo.Goods"></property> </bean> </set> </property> </bean>
在这里我使用的是
org.springframework.cache.support.SimpleCacheManager;
网上很多都是用的
org.springframework.data.redis.cache.RedisCacheManager.
我使用另一个会一直报错,找了很久也没找到问题的原因,希望有大佬能告诉我。
<cache:annotation-driven cache-manager="cacheManager"></cache:annotation-driven>
这个必须在spring的主配置文件中,cache-manager的名字必须和application-redis中的cacheManager一致
然后开始编写操作缓存的实现类
//实现Cache接口 public class RedisCache<T> implements Cache { //缓存的名称 private String name; //返回的类型 private Class<T> type; //Spring提供的操作Reids的工具 private RedisTemplate<String,Object> redisTemplate; public void setName(String name) { this.name = name; } public RedisTemplate<String, Object> getRedisTemplate() { return redisTemplate; } public Class<T> getType() { return type; } public void setType(Class<T> type) { this.type = type; } public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public String getName() { return this.name; } @Override public Object getNativeCache() { return this.redisTemplate; } //通过key查询缓存 @Override public ValueWrapper get(Object key) { if (WoUtil.isEmpty(key)) { return null; }else { final String finalKey; if (key instanceof String) { finalKey = (String) key; }else { finalKey = key.toString(); } Object object = null; object = redisTemplate.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { byte[] key = finalKey.getBytes(); byte[] value = connection.get(key); if (value == null) { return null; } String val = new String(value); List<T> tList = JSONArray.parseArray(val, type); return tList; } }); return (object != null ? new SimpleValueWrapper(object) : null); } } @Override public <T> T get(Object key, final Class<T> type) { return null; } @Override public <T> T get(Object o, Callable<T> callable) { return null; } //向缓存存放数据 @Override public void put(Object key , Object value) { if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) { return; } else { final String finalKey; if (key instanceof String) { finalKey = (String) key; } else { finalKey = key.toString(); } if (!StringUtils.isEmpty(finalKey)) { final Object finalValue = value; redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) { String jsonStr = JSONArray.toJSONString(finalValue); connection.set(finalKey.getBytes(),jsonStr.getBytes()); return true; } }); } } } @Override public ValueWrapper putIfAbsent(Object o, Object o1) { return null; } //通过key删除缓存的数据 @Override public void evict(Object key) { if (WoUtil.isEmpty(key)) { return; }else { final String finalKey; if (key instanceof String) { finalKey = (String) key; }else { finalKey = key.toString(); } redisTemplate.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { return connection.del(finalKey.getBytes()); } }); } } @Override public void clear() { } }
这里面最主要的就是put和get方法,我只实现了put和get和一个evict就目前够用了,方法逻辑很简单,判断key是否为空,不为空就调用对应的方法,因为我都是获取的List,所有返回的是List,根据的自己的需求编写。
下面就是如何使用了,我以商品为例
//value对应redis配置中的value //key只要保证唯一就可以了,我这里用的root获取当前被调用的对象的Class @Override @Cacheable(value = "goodsCache", key = "#root.targetClass") public List<Goods> getList() { List<Goods> goodsList = goodsDaoImpl.findAll(); return goodsList; }
这样看起来代码就很优雅了,里面只有我们真正需要的代码了。
Spring提供的root对象的使用
属性名称
描述
示例
methodName
当前方法名
#root.methodName
method
当前方法
#root.method.name
target
当前被调用的对象
#root.target
targetClass
当前被调用的对象的class
#root.targetClass
args
当前方法参数组成的数组
#root.args[0]
caches
当前被调用的方法使用的Cache
#root.caches[0].name
第一次写博客,逻辑很乱,只会把自己做的东西给大家看看,也没怎么分析,实在是抱歉,有时间会多写写锻炼自己的。