<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
redis的安裝就不說了,在springboot中導入這兩個包。
在application入口加上這個註解@EnableCaching
@SpringBootApplication
@ServletComponentScan
@RestController
@EnableCaching
public class Application {
配置redis緩存,說實話是在網上找的。
package com.kq.highnet2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Redis緩存配置類
* @author szekinwin
*
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
//自定義緩存key生成策略
// @Bean
// public KeyGenerator keyGenerator() {
// return new KeyGenerator(){
// @Override
// public Object generate(Object target, java.lang.reflect.Method method, Object... params) {
// StringBuffer sb = new StringBuffer();
// sb.append(target.getClass().getName());
// sb.append(method.getName());
// for(Object obj:params){
// sb.append(obj.toString());
// }
// return sb.toString();
// }
// };
// }
//緩存管理器
@Bean
public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
//設置緩存過期時間
cacheManager.setDefaultExpiration(10000);
return cacheManager;
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){
StringRedisTemplate template = new StringRedisTemplate(factory);
setSerializer(template);//設置序列化工具
template.afterPropertiesSet();
return template;
}
private void setSerializer(StringRedisTemplate template){
@SuppressWarnings({ "rawtypes", "unchecked" })
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
}
}
然後從網上下載redis desktop manager,可以查看redis
這個還是很有必要的,這樣就很好地瞭解緩存的具體位置,順便一提,已經使用了spring-session,作爲分佈式服務器的通用session
下面就是具體的使用了,總共有五個註解@Cacheable、@CacheEvict、@CachePut、@Caching、@CacheConfig
因爲此類內容網上一大堆,我也就不搬運了,就說一下我個人的使用心得和感受吧,這些標籤都有通用值,重要的有value和key
@Cacheable(value="baseUserModel",key="#p0")
BaseUserModel findByUserId(String baseUserId);
結合數據庫管理器就很清楚了
把數據保存到了baseUserModel裏面,並且以方法的參數爲key。就是說緩存是以方法的參數作爲key值而不能以返回值的某一項(比如id)作爲key值,保存緩存和刪除緩存同樣如此。實際上這樣侷限性很大,因爲很多方法並不是用id作爲參數的,而是通過多項條件做爲參數的。
緩存最重要的一點就是你必須可以修改緩存或者刪除它,這種時機的把握很重要,如果只能用方法的參數作爲key鍵,而更新操作一般使用的參數與查詢不同,所以實際上最適用的範圍還是使用id查詢單個對象,我想到的另一種方法就是專門創建刪除緩存的方法,專門傳入查詢的參數,加上@CachePut或@CacheEvict註解。比如:
@CacheEvict(value="userList",key= "#account.concat(#gender)")
void deleteCache(String account, String gender);
或者 @CachePut(value="baseUserModel",key="#baseUser.userId")
public BaseUserModel updateCache(BaseUserModel baseUser) {
return baseUser;
}
要注意的是@CachePut需要返回值,不然就會刪除緩存,雖然也沒什麼關係就是了。
我有個業務需要從好幾個不同的表裏拿出數據拼接起來,所以cache註解需要加載dao層上,然後發現一個問題
@Cacheable(value="baseDepartmentModel",key="#departmentId")
在接口上使用 #參數名 的形式是不行的,它只會拿到null,而是要使用@Cacheable(value="baseDepartmentModel",key="#p0")
@Cacheable(value="baseDepartmentModel",key="#root.args[0].userId")
通過參數的位置去拿。
又發現一個問題我有一個用戶表BaseUserModel和僱員表BaseEmployeeModel,僱員表直接使用的是用戶表的id,結果當我直接使用
@Cacheable(value="baseEmployeeModel",key="#p0")
BaseEmployeeModel findByEmployeeId(String baseUserId);
竟然報轉化錯誤了
java.lang.ClassCastException: com.kq.highnet2.framework.base.baseUser.model.BaseUserModel cannot be cast to com.kq.highnet2.framework.base.baseUser.model.BaseEmployeeModel
at com.sun.proxy.$Proxy166.findByEmployeeId(Unknown Source)
at com.kq.highnet2.framework.base.baseUser.service.impl.BaseUserMainServiceImpl.getUserById(BaseUserMainServiceImpl.java:706)
at com.kq.highnet2.framework.base.baseUser.service.impl.BaseUserMainServiceImpl$$FastClassBySpringCGLIB$$8709dccc.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
也就是說redis在查找緩存時,不是先通過value找到對應位置再通過key來獲得對象,而是直接通過key來找到對象
那麼註解的value值實際上並沒有什麼意義,最多起到一個管理key值的作用,那麼如果不同對象擁有相同key值的情況怎麼辦呢?在前面加上專門的前綴就行了,實際上在寫html給id取名時也是如此,加上足夠複雜的前綴或後綴防止重複
@Cacheable(value="baseEmployeeModel",key="'employee'+#p0")
BaseEmployeeModel findByEmployeeId(String baseUserId);
之前主管讓我不要使用關聯查詢的語句,現在的用意我也知道了,當使用緩存的時候,直接保存關聯查詢的結果,那麼一旦某個類更新了,則需要刪除所有關聯查詢語句的緩存。
比如,我的計劃單,任務單,都有產品的id,之前使用關聯查詢拿到產品的名稱,一旦產品改變名字了,如果我使用緩存,那麼所有的緩存都要刪除,重新查詢。但是如果我吧每個對象獨立保存,使用的時候根據id查詢出產品,然後賦值給相應的字段,那麼我更新產品時也只要更新產品的緩存就行了。雖然侷限性還是很大的