-
添加pom文件支持
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.sg</groupId> <artifactId>cristina</artifactId> <version>0.0.1-SNAPSHOT</version> <name>cristina</name> <description>El psy congroo</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.46</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.0.0</version> </dependency> <!-- dubbo相关 --> <!-- zookeeper --> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.8</version> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <!-- Dubbox --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.8.4</version> </dependency> <!-- springboot的注解处理器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!--<scope>runtime</scope>--> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.22</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- mybatis generator 自动生成代码插件 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile> <overwrite>true</overwrite> <verbose>true</verbose> </configuration> </plugin> </plugins> </build> </project>
-
springboot开启AOP
-
@EnableAspectJAutoProxy @SpringBootApplication @MapperScan("com.sg.cristina.dao") public class CristinaApplication { public static void main(String[] args) { SpringApplication.run(CristinaApplication.class, args); } }
-
自定义用于添加缓存的注解和用户删除缓存的注解
/** * Created by jw on 19/3/29. * @usage 缓存注解类 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface Cacheable { String key(); String fieldKey() ; int expireTime() default 3600; } /** * Created by jw on 19/3/29. * * @usage 清除过期缓存注解,放置于update delete insert 类型逻辑之上 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CacheEvict { String key(); String fieldKey() ; int expireTime() default 3600; }
-
切面逻辑
import java.lang.reflect.Method; /** * Created by jw on 19/3/29. */ @Aspect @EnableAspectJAutoProxy @Component public class RedisCacheAspect { private static final Logger logger = Logger.getLogger(RedisCacheAspect.class); /** * 分隔符 **/ private static final String DELIMITER = "|"; @Autowired RedisUtil redisUtil; /** * Service层切点 使用到了我们定义的 Cacheable 作为切点表达式。 * 而且我们可以看出此表达式基于 annotation。 * 并且用于内建属性为查询的方法之上 */ @Pointcut("@annotation(com.sg.cristina.config.aspect.Cacheable)") public void queryCache() { } /** * Service层切点 使用到了我们定义的 RedisEvict 作为切点表达式。 * 而且我们可以看出此表达式是基于 annotation 的。 * 并且用于内建属性为非查询的方法之上,用于更新表 */ @Pointcut("@annotation(com.sg.cristina.config.aspect.CacheEvict)") public void ClearCache() { } @Around("queryCache()") public Object Interceptor(ProceedingJoinPoint pjp) throws Throwable { Object result = null; System.out.println("query cache -------------------------------"); Method method = getMethod(pjp); //获取被切方法的注解 Cacheable cacheable = method.getAnnotation(Cacheable.class); //key的value String fieldKey = parseKey(cacheable.fieldKey(), method, pjp.getArgs()); //使用redis 的hash进行存取,类似于一张表 result = redisUtil.hget(cacheable.key(), fieldKey); if (result == null) { //如果缓存没有则执行原本逻辑 System.out.println("cache is empty ,query db--------"); try { result = pjp.proceed(); System.out.println("get result from db ----------"); //从db查询到了则再加入缓存 if (result != null) { redisUtil.hset(cacheable.key(), fieldKey, result); System.out.println("set result to cache ----------"); } } catch (Throwable e) { e.printStackTrace(); logger.error(e.getMessage()); } } return result; } /*** 定义清除缓存逻辑*/ @Around(value = "ClearCache()") public Object evict(ProceedingJoinPoint pjp) throws Throwable { Object result = null; System.out.println("clear cache ---------------"); Method method = getMethod(pjp); CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class); String fieldKey = parseKey(cacheEvict.fieldKey(), method, pjp.getArgs()); //先删除缓存 redisUtil.hdel(cacheEvict.key(), fieldKey); //然后操作db result = pjp.proceed(); return result; } /** * 获取被拦截方法对象 * <p> * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 * 而缓存的注解在实现类的方法上 * 所以应该使用反射获取当前对象的方法对象 */ public Method getMethod(ProceedingJoinPoint pjp) { //获取参数的类型 Object[] args = pjp.getArgs(); Class[] argTypes = new Class[pjp.getArgs().length]; for (int i = 0; i < args.length; i++) { argTypes[i] = args[i].getClass(); } Method method = null; try { Object target = pjp.getTarget(); Class<?> aClass = target.getClass(); Signature signature = pjp.getSignature(); method = aClass.getMethod(signature.getName(), argTypes); } catch (NoSuchMethodException e) { e.printStackTrace(); logger.error(e.getMessage()); } catch (SecurityException e) { e.printStackTrace(); logger.error(e.getMessage()); } return method; } /** * 获取缓存的key * key 定义在注解上,支持SPEL表达式 * * @param * @return */ private String parseKey(String key, Method method, Object[] args) { //获取被拦截方法参数名列表(使用Spring支持类库) LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); String[] paraNameArr = u.getParameterNames(method); //使用SPEL进行key的解析 ExpressionParser parser = new SpelExpressionParser(); //SPEL上下文 StandardEvaluationContext context = new StandardEvaluationContext(); //把方法参数放入SPEL上下文中 for (int i = 0; i < paraNameArr.length; i++) { context.setVariable(paraNameArr[i], args[i]); } return parser.parseExpression(key).getValue(context, String.class); } }
-
注解使用方式及注意事项
-
这种注解方式适用于单表的增删改查,一般以id(唯一标示)作为缓存的要素,如果将连表查询的结果缓存到redis那么对缓存的对象有一定的要求,如:读的频率高,且改动少。
-
本次采用的缓存逻辑是在service层对select类方法进行先查缓存再查db处理,如果缓存没查到,那么执行db查询,然后将查到的结果缓存到redis,对update的方法和delete的方法统一采用先删除缓存然后修改db
-
使用方法参考:
/** * @Author: jiangwei * @Date: 2019/4/20 * @Desc: */ @Service public class UserServiceImpl implements UserService { @Autowired SgUserMapper userMapper ; /** * @param sgId * @mbggenerated */ @CacheEvict(key = "SgUser",fieldKey = "#sgId") @Override public int deleteByPrimaryKey(Integer sgId) { return userMapper.deleteByPrimaryKey(sgId); } /** * @param record * @mbggenerated */ @Override public int insert(SgUser record) { return userMapper.insert(record); } /** * @param record * @mbggenerated */ @CacheEvict(key = "SgUser",fieldKey = "#record.sgId") @Override public int insertSelective(SgUser record) { return userMapper.insertSelective(record); } /** * @param sgId * @mbggenerated */ @Cacheable(key = "SgUser",fieldKey = "#sgId") @Override public SgUser selectByPrimaryKey(Integer sgId) { return userMapper.selectByPrimaryKey(sgId); } /** * @param record * @mbggenerated */ @CacheEvict(key = "SgUser",fieldKey = "#record.sgId") @Override public int updateByPrimaryKeySelective(SgUser record) { return userMapper.updateByPrimaryKeySelective(record); } /** * @param record * @mbggenerated */ @CacheEvict(key = "SgUser",fieldKey = "#record.sgId") @Override public int updateByPrimaryKey(SgUser record) { return userMapper.updateByPrimaryKey(record); } @Override public SgUser selectByGitId(String gitId) { return userMapper.selectByGitId(gitId); } }
-