基于注解的spring缓存,轻松无侵入解决cache问题

之前怎么加缓存

为了加快数据获取速度,减少数据库的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秒。 输入图片说明

参考资料

转载于:https://my.oschina.net/thinwonton/blog/918163

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值