基于注解的spring缓存,轻松无侵入解决cache问题
2017-06-23 09:45
591 查看
之前怎么加缓存
为了加快数据获取速度,减少数据库的I/O压力,往往在业务接口中加入缓存。相信大多数人都是按照下面的方法中操作缓存:public User getUserById(long id) { //缓存中获取数据 if (Cache.contain(CacheKey.get(id))) { return Cache.get(id); } //数据库获取数据 User user = userDao.getById(); Cache.put(CacheKey.get(id),user); return user; }
缓存的读取和保存已经侵入到业务代码中,后期很难维护。那么,是否有一种优雅的方法,无侵入地解决缓存问题呢?
spring提供了spring cache模块,利用注解式AOP的方式提供缓存支持。
实现
假设需要对用户业务接口的查询功能加入缓存,在更新用户信息和删除用户的时候清除缓存,保证数据一致性。为了演示,定义了用户业务类UserService。public interface UserService { User getById(long id); void deleteById(long id); void update(User user); }
###(1)业务添加缓存
配置cache的规则,例如key,cache的名字,什么情况下保存或者删除缓存。这些规则都支持SpEL表达式,关于SpEL表达式请自行了解。先看一下为了满足需求,对业务的缓存配置如下:
@Service @CacheConfig(cacheNames = {"userCache"}) public class UserServiceImpl implements UserService { @Cacheable(key = "'user:'+#id", unless = "#result == null") @Override public User getById(long id) { System.out.println("getById():" + id); return new User(id, "hugo"); } @CacheEvict(key = "'user:'+#id") @Override public void deleteById(long id) { System.out.println("deleteById():" + id); throw new RuntimeException("deleteById()方法发生了异常"); } @CacheEvict(key = "'user:'+#user.id") @Override public void update(User user) { System.out.println("update():" + user.toString()); } }
上面使用名为userCache的缓存实例,为了演示,三个业务方法的缓存key都是以"user:id"的方式定义,这里使用SpEL表达式,spring会在运行时获取id值填入到表达式中。SpEL支持如下上下文数据:
** cache注解详细解释 **
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
@CacheEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空
(2)spring配置缓存
前面已经在业务方法中应用了缓存,那么接下来将在spring中定义该缓存实例。由于现在在项目中主要应用ehcache和redis作为缓存实现方案,所以下面分别对ehcache和redis两种方案的缓存进行配置。** ehcache **
清单:applicationContext-cache-ehcache.xml
<!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 --> <!--使用proxy方式,内部调用时,被内部调用的方法的缓存失效--> <!--使用aspect方式,需要按照https://my.oschina.net/thinwonton/blog/918006的方法配置--> <cache:annotation-driven cache-manager="cacheManager" mode="proxy" proxy-target-class="true"/> <!-- cacheManager工厂类,指定ehcache.xml的位置 --> <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:cache/ehcache.xml"/> </bean> <!-- 声明cacheManager --> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="cacheManagerFactory"/> </bean>
清单:ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false"> <!-- <diskStore path="java.io.tmpdir" /> --> <diskStore path="E:/cachetmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"/> <cache name="userCache" maxElementsInMemory="10000" maxElementsOnDisk="1000" eternal="false" diskPersistent="true" overflowToDisk="true" diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/> </ehcache>
** redis **
这里需要注意的是,key和value的生成方式。key 的生成器使用org.springframework.data.redis.serializer.StringRedisSerializer,该生成器获取到的key可以在任意redis客户端中通用;value使用GenericJackson2JsonRedisSerializer生成,因为jackson反序列化时需要指明类的类型,所以GenericJackson2JsonRedisSerializer将会在序列化的时候保存class类型到redis中。
另外,RedisCacheManager默认情况下是不支持单独对某个key(方法级别)设置过期时间的,这个需要进行扩展。配置文件中的expires属性的意思是对某个缓存实例设置过期时间。
清单:applicationContext-cache-redis.xml
<!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 --> <!--使用proxy方式,内部调用时,被内部调用的方法的缓存失效--> <!--使用aspect方式,需要按照https://my.oschina.net/thinwonton/blog/918006的方法配置--> <cache:annotation-driven cache-manager="cacheManager" mode="proxy"/> <!-- redis连接池的配置 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="1"/> <property name="maxTotal" value="5"/> <property name="blockWhenExhausted" value="true"/> <property name="maxWaitMillis" value="30000"/> <property name="testOnBorrow" value="true"/> </bean> <!-- 工厂类配置 --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="192.168.88.18"/> <property name="port" value="6379"/> <property name="poolConfig" ref="jedisPoolConfig"/> <property name="timeout" value="15000"/> <property name="usePool" value="true"/> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <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.GenericJackson2JsonRedisSerializer"/> </property> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/> </property> </bean> <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"> <constructor-arg ref="redisTemplate"/> <constructor-arg name="cacheNames"> <set> <!--声明userCache--> <value>userCache</value> </set> </constructor-arg> <!-- 是否在容器启动时初始化 --> <property name="loadRemoteCachesOnStartup" value="true"/> <!-- 是否使用前缀,规则是 cacheName:key--> <property name="usePrefix" value="true"/> <!-- 前缀命名,仅当usePrefix为true时才生效 --> <property name="cachePrefix"> <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix"> <constructor-arg name="delimiter" value=":"/> </bean> </property> <!-- 默认有效期1h --> <property name="defaultExpiration" value="3600"/> <property name="expires"> <map> <!--对cahce实例设置过期时间--> <entry key="userCache" value="10"/> </map> </property> </bean>
运行测试结果如下,在redis中存储了key为userCache:user:100的缓存数据,缓存的有效期是3600秒。
参考资料
Spring Cache抽象详解注释驱动的 Spring cache 缓存介绍
Spring Cache扩展
相关文章推荐
- Redis缓存实践:自定义注解、缓存工具类、基于Spring注解@cacheable
- 使用spring3.0的cache方案解决缓存问题
- spring boot整合shiro后,部分注解(Cache缓存、Transaction事务等)失效的问题
- Spring基于注解的缓存配置--web应用实例
- Spring基于注解的缓存配置
- 缓存初解(三)---Spring3.0基于注解的缓存配置+Ehcache和OScache
- Spring缓存注解@Cache,@CachePut , @CacheEvict,@CacheConfig使用
- Spring基于注解整合Hibernate EhCache实现对象缓存
- Spring基于注解的缓存配置--EHCache AND OSCache
- 解决linux下dns缓存问题 dnscache.py
- spring 启动后执行自我操作(如加入数据进入缓存) 基于注解
- 基于md5的解决js,css缓存问题for django
- 解决构选不确定问题,使用spring3注解
- 基于Unitils和Spring解决一些单元测试的常见问题
- spring注解实现业务层事务管理,当业务层自调用时,事务失效问题解决
- 基于注解的Spring MVC与JPA如何解决实体的延时加载问题
- spring3.0基于注解的定时器以及执行两次的解决办法
- Spring基于注解的缓存配置--web应用实例
- 解决spring-mvc @responseBody注解返回json 乱码问题
- 关于spring 的@cacheable 注解 无法生效的问题