為什么要用spring cache
平日我們的查詢代碼一般如下:
public Cat getCatByName(String name) {
//從緩存中查數(shù)據(jù)
String key = "Cat:" + name;
byte[] bytes = redis.getBytes(key);
if (bytes != null) {
return protostuffSerializer.deserialize(bytes);
}
//如果緩存中找不到數(shù)據(jù),從數(shù)據(jù)中獲取
String sql = "select * from cat where name = ?";
List<Cat> cats = jdbcTemplate.query(sql, name, new BeanPropertyRowMapper<>(Cat.class));
Cat cat = null;
if(cats != null && cats.size() > 0){
cat = cats.get(0);
}
//設(shè)置緩存
redis.setex(key, DateUtils.ONE_HOURS_SECOND, protostuffSerializer.serialize(cat));
return cat;
}
????寫(xiě)多了代碼,會(huì)發(fā)現(xiàn)查詢的代碼一般都比較固定,即從先緩存中獲取數(shù)據(jù),如果緩存中沒(méi)有數(shù)據(jù)就從數(shù)據(jù)庫(kù)中獲取,然后再設(shè)置緩存.除了查詢代碼,添加或更新數(shù)據(jù)的代碼一般是先更新完數(shù)據(jù)庫(kù)后,再更新緩存;而刪除代碼,一般是刪除完數(shù)據(jù)庫(kù)后,再刪除完緩存.
????有沒(méi)有發(fā)現(xiàn)設(shè)置緩存的代碼一般也比較死,比較一樣.那有沒(méi)有辦法把緩存代碼抽離出來(lái),讓框架幫我們?nèi)プ?我們只需寫(xiě)數(shù)據(jù)庫(kù)的代碼就可以了呢?答案是有的.這就是spring cache.
spring cache 可能會(huì)怎么實(shí)現(xiàn)的
說(shuō)到將比較固定的代碼抽離出來(lái),比如日志?我們一般會(huì)想到什么呢?沒(méi)錯(cuò),就是AOP編程.在AOP編程中,緩存的設(shè)置可能會(huì)這么寫(xiě)
@Around(value = "execution(* com.test.*.*(..)) ")
public Object calculateTime(ProceedingJoinPoint joinPoint) throws Throwable {
//從緩存中獲取數(shù)據(jù)
//獲取參數(shù)
String key = getArg(point);
//從redis中獲取值
String value = redis. get(key);
if(StringUtils.isNotBlank(value)){
return value;
}
Object object = joinPoint.proceed();
//設(shè)置緩存
redis.put(key,object);
return object;
}
????假如讓我們用aop和redis設(shè)計(jì)一個(gè)緩存框架,我們會(huì)怎么做呢?
????對(duì)于查詢操作,我們會(huì)在方法體前面先用redis的get()出緩存,在方法體后面我們會(huì)用redis的put()保存緩存;對(duì)于保存和更新操作,我們會(huì)用redis的del()先刪掉緩存,再用put()把新緩存保存上去;而對(duì)于刪除操作,我們會(huì)在方法體后面用redis的del()刪除掉緩存.
????沒(méi)錯(cuò),spring cacheManager的api也差不多是這么做的.而大名鼎鼎的注解@Cacheable,@CachePut,@CacheEvict分別對(duì)應(yīng)上面三種情況.
注解
1 @Cacheable(value="hashMap",key = "'User:' + #name", condition = "#name.length() > 5")
作用 : 對(duì)查詢操作,當(dāng)調(diào)用加了@Cacheable注解的方法時(shí),jvm會(huì)根據(jù)配置的key去緩存中尋找是否有對(duì)應(yīng)的緩存,如果有就直接返回,如果沒(méi)有才會(huì)繼續(xù)執(zhí)行代碼,當(dāng)執(zhí)行完代碼后,jvm還會(huì)把返回結(jié)果保存到緩存中.
value : 緩存保存的位置, 對(duì)應(yīng)我們?cè)趚ml配置文件中配置的 name = "hashMap" 的緩存 .
key : 緩存都是以鍵值對(duì)的形式保存的,我們可以用#引用一些變量
condition : 條件, #name.length() > 5表示 當(dāng)key的長(zhǎng)度 大于5的時(shí)候緩存注解才生效
2 @CacheEvict(value="hashMap",key = "'User:' + #name",@beforeInvocation=true)
作用 : 對(duì)應(yīng)刪除操作,當(dāng)注解了@CacheEvict的方法被調(diào)用結(jié)束后,key = "'User:' + #name" 的緩存會(huì)被清除掉.
@beforeInvocation=true : 當(dāng)時(shí)如果方法還沒(méi)執(zhí)行完就報(bào)錯(cuò)跳出了,這時(shí)緩存并不會(huì)被清掉,如果我們想在方法執(zhí)行前就清掉緩存的話,我們可以配置一個(gè)beforeInvocation的注解
3 @CachePut(value="hashMap",key = "'User:' + #name")
作用 : 對(duì)應(yīng)保存和更新操作,注解了@CachePut的方法,執(zhí)行前會(huì)把key對(duì)應(yīng)的緩存清掉,執(zhí)行完方法以后會(huì)把結(jié)果保存到key上去.
spring cache 的簡(jiǎn)單實(shí)例
xml
<!-- 啟用緩存注解功能兔魂,這個(gè)是必須的,否則注解不會(huì)生效 -->
<cache:annotation-driven cache-manager="cacheManager" />
<!--spring cache 管理器 -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!--定義了緩存的實(shí)現(xiàn)方式,這里是基于HashMap的-->
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" >
<!--緩存的名字叫做hashMap,注解里的value對(duì)應(yīng)的值-->
<property name="name" value="hashMap"/>
</bean>
</set>
</property>
</bean>
Java
/**
* 我們執(zhí)行兩次該方法,會(huì)發(fā)現(xiàn)只有第一次輸出 first get, 第二次不輸出,說(shuō)明設(shè)置成功
*/
@Cacheable(value = "hashMap", key = "'User:' + #name")
public User getUserByName(String name){
System.out.println("first get");
User user = new User();
user.setName("HZP");
user.setAge(23);
return user;
}
Spring Cache 整合 Redis
同樣的我們先實(shí)現(xiàn)一個(gè)基于redis的緩存管理器
import com.v56.common.show.utils.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import redis.clients.jedis.PipelineCluster;
import redis.clients.jedis.serializable.ProtostuffSerializer;
public class RedisCache implements Cache {
private Logger logger = LoggerFactory.getLogger(getClass());
//redis工具
private PipelineCluster redis;
//序列化工具
private ProtostuffSerializer protostuffSerializer = null;
//緩存名字
private String name;
public void setRedis(PipelineCluster redis) {
this.redis = redis;
}
public void setName(String name) {
this.name = name;
}
public void setProtostuffSerializer(ProtostuffSerializer protostuffSerializer) {
this.protostuffSerializer = protostuffSerializer;
}
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this.redis;
}
//從redis中獲取緩存的操作
@Override
public ValueWrapper get(Object key) {
byte[] bytes = redis.getBytes((String) key);
return bytes != null ? new SimpleValueWrapper(protostuffSerializer.deserialize(bytes)) : null;
}
//把數(shù)據(jù)放到緩存中
@Override
public void put(Object key, Object value) {
redis.setex((String) key, DateUtil.ONE_DAY_SECOND, protostuffSerializer.serialize(value));
}
//刪除緩存
@Override
public void evict(Object key) {
redis.del((String) key);
}
//清除全部緩存
@Override
public void clear() {
}
}
還要再xml中定義,把我們RedisCache配置進(jìn)去即可,簡(jiǎn)單方便
<!-- 這里是我們公司自己定義的一套redis-->
<bean id="redisClusterFactory4fastjdbc" class="com.v56.qf.fastjdbc.web.RedisClusterFactory" init-method="init">
<property name="appId" value="${fastjdbc.redis.appId}"/>
<property name="maxTotal" value="${fastjdbc.redis.maxTotal}"/>
<property name="maxIdle" value="${fastjdbc.redis.maxIdle}"/>
<property name="minIdle" value="${fastjdbc.redis.minIdle}"/>
<property name="maxWaitMillis" value="${fastjdbc.redis.maxWaitMillis}"/>
</bean>
<bean id="redisCluster4fastjdbc" factory-bean="redisClusterFactory4fastjdbc" factory-method="getRedisCluster"/>
<bean id="protostuffSerializer4fastjdbc" class="redis.clients.jedis.serializable.ProtostuffSerializer"/>
<!--REDIS CONFIG For FastJDBC END -->
<!-- 啟用緩存注解功能翠霍,這個(gè)是必須的儒老,否則注解不會(huì)生效千劈,另外梯码,該注解一定要聲明在spring主配置文件中才會(huì)生效 -->
<cache:annotation-driven cache-manager="cacheManager" />
<!-- spring自己的換管理器吆鹤,這里定義了兩個(gè)緩存位置名稱 厨疙,既注解中的value -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!--這里是我們自己寫(xiě)的緩存管理器-->
<bean class="com.v56.qf.labor2018.cache.RedisCache">
<property name="redis" ref="redisCluster4fastjdbc" />
<property name="protostuffSerializer" ref = "protostuffSerializer4fastjdbc" />
<property name="name" value="redisCache" />
</bean>
</set>
</property>
</bean>
注意
PS: Spring cache 是基于aop proxy進(jìn)行操作的,在proxy代理中,如果是內(nèi)部調(diào)用,是不會(huì)走代理類的