您的位置:首页 > 编程语言 > Java开发

基于注解的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扩展
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring cache redis Ehcache