当我们在编写查询数据库Service时,需要现在redis中查询是否有所需数据的缓存,如果有,则无需去查询数据库,这无疑提高了查询速度同时减轻了数据库的压力。 在使用springboot整合redis时,相信大家都对@Cacheable这个注解不陌生吧 例如我们需要在Service查询数据库前先去缓存中查询,如果没有在查询数据库,有则不去查询,我们只需要在redisTemplate配置类中加入@EnableCaching注解,并配置Spring Cache注解功能,如下:
package com.sunyuqi.config; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @EnableCaching class RedisTemplateConfig { @Bean public LettuceConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379)); } @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); return redisTemplate; } // 配置Spring Cache注解功能 @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration); return cacheManager; } }然后在service方法上加上注解即可
@Cacheable(cacheManager = "cacheManager", value = "testcache", key = "#userId") public User findUserById(String userId) throws Exception { //此处编写查询数据库代码 User user = .... return user; }该方法执行前会先从redis缓存中查询,如果查询到结果,则该方法不会执行,如果没有结果,该方法才会执行。
当我们需要自定义缓存查询逻辑时,就需要定义注解了。
导入相关依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>首先我们需要定义一个注解
package com.sunyuqi.mycache.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * cache注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Cache { String key(); }该注解定义在方法上,并且需要传入一个参数key。 定义AOP
package com.sunyuqi.mycache.aop; import com.sunyuqi.mycache.annotations.Cache; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Component @Aspect public class CacheAspect { @Autowired private RedisTemplate redisTemplate; @Pointcut("@annotation(com.sunyuqi.mycache.annotations.Cache)") public void cachePointcut() { } // 定义相应的事件 @Around("cachePointcut()") public Object doCache(ProceedingJoinPoint joinPoint) { Object value = null; try { // 获取当前方法上注解的内容 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取被注解的方法 Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes()); Cache cacheAnnotation = method.getAnnotation(Cache.class); // 获取到传入注解中的key的值 即"#userID"(我们需要EL解析) String keyEl = cacheAnnotation.key(); // 创建解析器 ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(keyEl); EvaluationContext context = new StandardEvaluationContext(); // 添加参数 Object[] args = joinPoint.getArgs(); DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer(); String[] parameterNames = discover.getParameterNames(method); for (int i = 0; i < parameterNames.length; i++) { context.setVariable(parameterNames[i], args[i].toString()); } // 解析EL表达式 String key = expression.getValue(context).toString(); // 查询缓存,并判定缓存中是否存在 value = redisTemplate.opsForValue().get(key); if (value != null) { System.out.println("从缓存中查询到结果:" + value); return value; } // 缓存中没有结果,执行被注解的方法 value = joinPoint.proceed(); // 将结果存入缓存 redisTemplate.opsForValue().set(key, value); } catch (Throwable throwable) { throwable.printStackTrace(); } return value; } }在方法上使用自定义注解
@Cache(key = "#userId") public User findUserById(String userId) throws Exception { //此处编写查询数据库代码 User user = .... return user; }使用方法是不是很简单? 当然别忘了配置redistemplate
package com.sunyuqi.mycache.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Profile; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @EnableAspectJAutoProxy class MyRedisConfig { @Bean public LettuceConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379)); } @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); return redisTemplate; } }这里有一个点要提醒大家: 我们存入redis的对象实际上是序列化后的字符串。 在反序列化的过程中,也就是我们在从redis中查询缓存,并转化为对象时,如果当前该对象所在的包路径与存入时对象的包路径不同,则会反序列化失败。