1、导入依赖
org.springframework.boot spring-boot-starter-data-redis
2、添加redis配置
spring.redis.password=123456
此时已经可以在代码中使用redisTemplate对象了。例如:
@Service public class DepartmentServiceImpl implements DepartmentService { @Resource private DepartmentDAO departmentDAO; @Resource private RedisTemplate redisTemplate; @Override public ListfindAll() { // json处理的对象 ObjectMapper mapper = new ObjectMapper(); // 1、查询缓存 // 定义一个key(唯一、尽量不会被推导) // 使用一个唯一的名字 + 参数值 + md5 String key = MD5Utils.md5("findAllDepartments"); // 得到redisTemplate中的操作对象 BoundValueOperations boundValueOps = redisTemplate.boundValueOps(key); // 得到缓存中的值 String value = boundValueOps.get(); System.out.println("value===" + value); // 2、判断缓存是否有该数据 if (value != null){ // 3、如果有则返回该数据 System.out.println("查询缓存"); List list = null; try { list = mapper.readValue(value, new TypeReference >(){}); }catch (Exception e){ e.printStackTrace(); } return list; }else{ // 4、如果没有,则去数据库中查询,并保存到缓存中 System.out.println("查询数据库"); List
departments = departmentDAO.findAll(); if (departments != null && departments.size() > 0){ // 转换成json格式 try { String string = mapper.writeValueAsString(departments); // 保存到缓存中 boundValueOps.set(string); }catch (Exception e){ e.printStackTrace(); } } return departments; } } }
但是,查看redis中存储的数据发现,key和value前面都有一段看似乱码的数据,而且中文也进行编码。为了解决该问题,需要参考前面ssm中整合redis的方案,对redis的key和value进行配置。
3、添加redis的设置
@Configuration public class MyRedisConfig { @Bean public RedisTemplateredisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template = new StringRedisTemplate(factory); 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); template.afterPropertiesSet(); return template; } }
再使用前面的service代码进行测试,发现上述问题已解决。
直接编写redis的逻辑,相对代码比较复杂,而且逻辑基本固定,可以使用缓存注解来简化该流程。
spring框架提供一套缓存注解:
@Cacheable:表示当前查询结果会使用缓存。(如果有缓存,直接使用,否则进行数据库查询并放入缓存) @CachePut:表示当前操作的数据的结果会更新到缓存中。(如果修改的数据在缓存存在,会同时修改缓存中的数据) @CacheEvict:表示如果删除的数据在缓存中,也会对应的删除。
1、添加缓存注解的配置
@Configuration // 配置文件 @EnableCaching // 允许缓存使用,开启缓存的配置,需要继承CachingConfigurerSupport类 public class MyRedisConfig extends CachingConfigurerSupport { @Bean public CacheManager cacheManager(RedisConnectionFactory factory){ RedisSerializerkeyRedisSerializer = new StringRedisSerializer(); // redis的key序列化方式 Jackson2JsonRedisSerializer valueRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); // redis的value的序列化 //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); valueRedisSerializer.setObjectMapper(om); //配置序列化(解决乱码的问题) RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ZERO) // 默认生存时间 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keyRedisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueRedisSerializer)) .disableCachingNullValues(); //缓存配置map Map cacheConfigurationMap=new HashMap<>(); //自定义缓存名,后面使用的@Cacheable的CacheName cacheConfigurationMap.put("myRedis",config); // cacheConfigurationMap.put("default",config); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .withInitialCacheConfigurations(cacheConfigurationMap) .build(); return cacheManager; } @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template = new StringRedisTemplate(factory); 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); template.afterPropertiesSet(); return template; } }
4、使用
@Service public class EmployeeServiceImpl implements EmployeeService{ @Resource private EmployeeDAO employeeDAO; @Override public PageInfo findAll(Integer page) { PageHelper.startPage(page, 10); Listlist = employeeDAO.findAll(); PageInfo info = new PageInfo(list); return info; } @Override // @Cacheable将查询结果进行缓存 // key的定义: // T()里面写一个类型,表示当作一个类处理,后面调用该类方法 // 如果是字符串,需要使用单引号 // 如果要使用方法中的参数,需要在变量名前面加上# @Cacheable(cacheNames = "myRedis", key = "T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)") public Employee findById(Integer id) { System.out.println("进入方法,去数据库查询"); return employeeDAO.findById(id); } @Override public void save(Employee employee) { employeeDAO.save(employee); } @Override // @CachePut将返回值作为数据替换掉原来的缓存数据 @CachePut(cacheNames = "myRedis", key = "T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #employee.id)") public Employee update(Employee employee) { employee.setUpdatetime(new Date()); employeeDAO.update(employee); return employee; } @Override // @CacheEvict删除缓存 @CacheEvict(cacheNames = "myRedis", key = "T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)") public void delete(Integer id) { employeeDAO.delete(id); } }
为了让缓存使用方式通用,选择了使用注解的方案,即@Cacheable, @CachePut等。
思考一下缓存中需要处理的问题,在使用注解过程中是否得以解决。
问题如下:缓存击穿,缓存穿透,大量的key过期导致的雪崩问题。
1、缓存击穿问题的原因是同一时间大量的请求访问,导致访问到数据库,可以使用缓存预热来解决。或者对请求使用削峰。【已解决】
2、穿透问题的原因是无法缓存null值,导致每次都访问数据库。解决问题的方案需要缓存空值。而且需要设置一个较短的过期时间,相较于非空数据的时间要短很多。【未解决】
3、大量key过期导致的缓存雪崩问题。原因主要是由于缓存时间设置一致。解决方案是设置不同的时间。【未解决】
1、在@Cache注解中使用unless属性。
@Cacheable(value=“XXX”,key=“#info”,unless = “#result==null”)
上面的办法能够解决空值出现的异常问题,但是又出现了新的问题------缓存穿透,每一次都会访问数据库。
2、使用自定义的AOP方案,切入到缓存存储的时机,自定义过程。
【方式比较麻烦,而且中间还会产生aop失效问题https://my.oschina.net/guangshan/blog/1807721】
3、自己使用redisTemplet的opsForValue().set()方法。
可能需要写很多次代码,也比较麻烦,而且没有充分利用到@Cacheable注解。
找到了一个分布式落地方案:layering-cache
https://my.oschina.net/xiaolyuh/blog/2245782
1、导入启动器
org.springframework.boot spring-boot-starter-data-redis com.github.xiaolyuh layering-cache-starter 3.1.8 org.springframework spring-aspects com.esotericsoftware.kryo kryo 2.21 com.alibaba fastjson 1.2.47
2、在application.properties中添加配置
#layering-cache 配置 layering-cache.stats=true # 缓存命名空间,如果不配置取 "spring.application.name" layering-cache.namespace=layering-cache-web # redis单机 layering-cache.redis.database=0 layering-cache.redis.host=127.0.0.1 layering-cache.redis.port=6379 layering-cache.redis.password= # redis集群 #layering-cache.redis.password= #layering-cache.redis.cluster=127.0.0.1:6379,127.0.0.1:6378
3、配置类中添加注解@EnableLayeringCache启用layering-cache
@SpringBootApplication @EnableLayeringCache // 启用多级缓存框架 public class LayeringCacheStartDemoApplication { public static void main(String[] args) { SpringApplication.run(LayeringCacheStartDemoApplication.class, args); } }
4、使用注解配置
参考:https://my.oschina.net/xiaolyuh/blog/2245782
不需要使用MyRedisConfig配置类。
import com.github.xiaolyuh.annotation.*; @Service public class EmployeeServiceImpl implements EmployeeService { @Resource private EmployeeDAO employeeDAO; @Override public ListfindAll() { return employeeDAO.findAll(); } @Override @Cacheable(value = "employee:info", key = "T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)" , depict = "用户信息缓存", enableFirstCache = true, firstCache = @FirstCache(expireTime = 400, timeUnit = TimeUnit.SECONDS), secondaryCache = @SecondaryCache(expireTime = 1000, preloadTime = 200, forceRefresh = true, timeUnit = TimeUnit.SECONDS, isAllowNullValue = true, magnification = 100)) public Employee findById(Integer id) { System.out.println("查询数据库"); return employeeDAO.findById(id); } @Override @CachePut(value = "employee:info", key = "T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)" , depict = "用户信息缓存", enableFirstCache = true, firstCache = @FirstCache(expireTime = 400, timeUnit = TimeUnit.SECONDS), secondaryCache = @SecondaryCache(expireTime = 1000, preloadTime = 200, forceRefresh = true, timeUnit = TimeUnit.SECONDS, isAllowNullValue = true, magnification = 100)) public Employee update(Employee employee) { employee.setUpdatetime(new Date()); employeeDAO.update(employee); return findById(employee.getId()); } @CacheEvict(value = "employee:info", key = "T(com.qf.sbems.utils.MD5Utils).md5('EmployeeService_findById' + #id)") public void delete(Integer id) { // employeeDAO.delete(id); } }