<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查询出产品,然后赋值给相应的字段,那么我更新产品时也只要更新产品的缓存就行了。虽然局限性还是很大的