1.商品分类缓存实现
1.当用户点击商品分类按钮时开始获取服务端数据.
2.先查询redis缓存是否有数据
3.如果redis中没有数据,则查询数据库.之后将查询的结果保存到redis中
4.如果redis中有数据,则直接返回缓存记录.
思路:
准备String key =“ITEM_CAT_LIST::”+parentId
准备value="JSON"串
首先查询redis缓存
有:返回数据
没有:查询数据库,并返回缓存和数据
1.1编辑ItemCatController 修改findItemCatByParentId方法
@RequestMapping("/list")
public List<EasyUITree> findItemCatByParentId(@RequestParam(value = "id",defaultValue = "0")Long parentId) {
//通过缓存的方式获取数据
return itemCatService.findItemCatByCache(parentId);
}
1.2在ItemCatServiceImpl添加方法
//spring容器初始化时,(required=false)该注解不是必须注入,但是如果程序调用则必须有值
@Autowired(required=false)
private Jedis jedis;
/**
* 通过缓存的方式查询数据库.
* 1).定义key
* 2).根据key查询redis.
*/
@SuppressWarnings("unchecked")
@Override
public List<EasyUITree> findItemCatByCache(Long parentId) {
//1.定义key
String key ="ITEM_CAT_LIST::"+parentId;
List<EasyUITree> treeList =new ArrayList<EasyUITree>();
Long startTime =System.currentTimeMillis();
//2.判断redis是否有值
if (jedis.exists(key)) {
//不是第一次查询,获取缓存数据直接返回数据
String json= jedis.get(key);
Long endTime =System.currentTimeMillis();
treeList = ObjectMapperUtil.toObject(json, treeList.getClass());
System.out.println("redis查询缓存时间为:"+(endTime-startTime)+"毫秒");
}else{//redis中没有key,用户第一次查询,让他去查询数据库
treeList = findItemCatByParentId(parentId);
Long endTime =System.currentTimeMillis();
//需要将list集合转化为json(调用ObjectMapperUtilAPI)
String json= ObjectMapperUtil.toJSON(treeList);
//将数据保存到redis中
jedis.set(key, json);
System.out.println("redis查询数据库时间为:"+(endTime-startTime)+"毫秒");
}
return treeList;
}
1.3速度测试
2.利用AOP实现redis缓存
2.1传统项目弊端
说明:
1).由于将redis的操作写到service层中,必须导致业务的耦合性高
2).如果采用上述的方式完成缓存,则改缓存不通用,并且代码冗余.效率低.
2.2 AOP的核心理念
公式: AOP = 切入点表达式 + 通知方法
2.3 切入点表达式
1). bean(bean的ID) 按照指定的bean名称拦截用户的请求,之后执行通知方法. 只能匹配单个bean对象
2).within(包名.类名) 可以按照类通配的方式去拦截用户的请求. 控制粒度较粗.
3).execution(返回值类型 包名.类名.方法名(参数列表)) 方法参数级别 控制粒度较细
4).@annotation(包名.注解名称) 按照注解的方式去拦截用户请求.
2.4 通知方法
1.前置通知: 主要在 目标方法执行之前执行
2.后置通知: 在目标方法执行之后执行
3.异常通知: 在目标方法执行的过程中报了异常之后执行.
4.最终通知: 无论什么时候都要执行的通知方法.
上述的通知方法,无法控制目标方法是否执行.所以一般"只做记录不做改变"
5.环绕通知: 一般采用环绕通知 实现对业务的控制.
2.5 AOP入门案例
package com.jt.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//1.将对象交给容器管理
@Component
//2.定义AOP切面
@Aspect
public class CacheAOP {
//公式: 切面=切入点表达式+通知方法.
/**
* 业务需求:要求拦截ItemCatServiceImpl类中的业务方法
* @Pointcut 切入点表达式 可以理解为一个if判断,只有满足条件(在itemCatServiceImpl类中)才可以执行方法
*/
//@Pointcut("bean(itemCatServiceImpl)") //按类匹配,控制的力度较粗 单个bean
//@Pointcut("within(com.jt.service.*)") //按类匹配,控制的力度较粗 多个bean
@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式
@Pointcut("@annotation(com.jt.anno.CacheFind)") //拦截注解 //不需要获取注解里的内容用这个方法
public void pointCut() {
}
//JoinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.
//@Before("bean(itemCatServiceImpl)") //一个切入点本次用
@Before("pointCut()") //前置通知(绑定切入点表达式)//一个切入点可以共同使用
public void before(JoinPoint joinpoint) {
System.out.println("前置通知(绑定切入点表达式)");
String typeName=
joinpoint.getSignature().getDeclaringTypeName();
String methodName=joinpoint.getSignature().getName();
System.out.println("方法的全路径为:"+typeName+"."+methodName);
Object[] obj=joinpoint.getArgs();
System.out.println("获取方法参数:"+obj);
Object target =joinpoint.getTarget();
System.out.println("获取目标对象:"+target);
}
//添加环绕通知
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("我是环绕通知开始");
try {
Object result = joinPoint.proceed();
System.out.println("我是环绕通知结束");
return result;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
2.6实现AOP缓存处理
package com.jt.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) //对谁有效
@Retention(RetentionPolicy.RUNTIME) //有效期
public @interface CacheFind {
public String key(); //标识存入redis的key的前缀
public int secondes() default 0; //标识保存时间 单位秒
}
2.6.1标识注解
2.6.2修改CacheAOP
package com.jt.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import redis.clients.jedis.Jedis;
//1.将对象交给容器管理
@Component
//2.定义AOP切面
@Aspect
public class CacheAOP {
@Autowired(required=false)
private Jedis jedis;
/**
* 实现思路: 拦截被@CacheFind标识的方法之后利用AOP进行缓存的控制
* 通知方法: 环绕通知
* 实现步骤:
* 1.准备查询redis的key ITEM_CAT_LIST::第一个参数
* 2.("@annotation(cacheFind)")动态获取注解的语法.
* 拦截指定注解类型的注解并且将注解对象当做参数进行传递.
*/
@SuppressWarnings("unchecked") //压制警告
//@Around("@annotation(com.jt.anno.CacheFind)")
@Around("@annotation(cacheFind)") //动态获取注解的语法
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) {
//1.获取用户注解中的key ITEM_CAT_LIST
String key =cacheFind.key();
//2.动态获取第一个参数当做key
//joinPoint.getArgs() 获取目标方法的参数|[0]第一个参数Long parentId
Object firstArg=joinPoint.getArgs()[0].toString();
key +="::"+firstArg;
Object result = null;
//3.根据key查询redis
if(jedis.exists(key)) {
//根据redis获取信息
String json=jedis.get(key);
MethodSignature methodSignature=(MethodSignature) joinPoint.getSignature();//getSignature得到方法对象的参数
result = ObjectMapperUtil.toObject(json,methodSignature.getReturnType());
System.out.println("aop查询redis缓存");
}else {
//如果key不存在,则证明是第一次查询,应该查询数据库
try {
result=joinPoint.proceed();
System.out.println("aop查询数据库获取返回值");
//将数据保存到redis中
String json = ObjectMapperUtil.toJSON(result);
int seconds=cacheFind.secondes();
if(seconds>0)
jedis.setex(key, seconds, json);
else
jedis.set(key, json);
}catch(Throwable e){
e.printStackTrace();
}
}
return result;
}
}
2.6.3修改ItemCatServiceImpl
实现商品列表查询时的缓存处理。