添加pom文件支持
4.0.0
com.sg
cristina
0.0.1-SNAPSHOT
cristina
El psy congroo
org.springframework.boot
spring-boot-starter-parent
2.0.3.RELEASE
UTF-8
UTF-8
1.8
commons-io
commons-io
2.4
commons-lang
commons-lang
2.5
com.alibaba
druid
1.1.9
com.alibaba
fastjson
1.2.46
org.springframework.boot
spring-boot-starter-web
com.github.pagehelper
pagehelper
4.0.0
org.apache.zookeeper
zookeeper
3.4.8
com.github.sgroschupf
zkclient
0.1
com.alibaba
dubbo
2.8.4
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework.boot
spring-boot-starter-test
test
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
mysql
mysql-connector-java
org.projectlombok
lombok
1.16.22
io.springfox
springfox-swagger2
2.7.0
io.springfox
springfox-swagger-ui
2.7.0
org.aspectj
aspectjweaver
1.8.13
org.springframework.boot
spring-boot-starter-data-redis
redis.clients
jedis
2.8.1
org.apache.httpcomponents
httpclient
4.5
org.springframework.boot
spring-boot-maven-plugin
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.2
${basedir}/src/main/resources/generator/generatorConfig.xml
true
true
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;
}
/**
* 获取被拦截方法对象
*
* 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);
}
}