Redis缓存--springboot整合

Redis缓存机制

二、springboot整合Redis

1.Redis入门案例

1.1导入jar包
<!--spring整合redis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>
1.2客户端操作Redis String类型命令 Test测试
package com.jt.test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

//@SpringBootTest //目的:动态获取spring容器中的数据
public class TestRedis {
    /**
     * 主要目的测试程序远程操作Redis是否有效
     * 配置redis服务:
     *      1.redis需要关闭IP绑定模式
     *      2.redis关闭保护模式
     *      3.redis最好开启后端运行
     *
     * 完成redis客户端操作
     */
    @Test
    public void test01() throws InterruptedException {
        //1.测试链接
        Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.set("a", "动态获取redis中的数据");
        System.out.println(jedis.get("a"));

        //2.测试数据是否存在
        if(jedis.exists("a")){
            jedis.set("a", "修改数据");
        }else{
            jedis.set("a", "新增数据");
        }

        //3.删除redis
        jedis.del("a");

        //4.清空所有的数据
        jedis.flushDB();
        jedis.flushAll();

        //5.为数据添加超时时间
        jedis.set("b", "设定超时时间");
        jedis.expire("b", 10);
        Thread.sleep(2000);
        System.out.println(jedis.ttl("b"));
    }

    //原子性
    @Test
    public void test02(){
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        jedis.set("c", "测试redis");
        //需求1: 如果数据不存在时,才会为数据赋值.
        jedis.setnx("d","测试setnx方法");
        System.out.println(jedis.get("d"));

        //需求2: 需要为数据添加超时时间,同时满足原子性的要求
                //jedis.set("s", "为数据添加超时时间");
                //有时程序中断了,下列的方法将不会执行.
                //jedis.expire("s", 20);
                //System.out.println(jedis.ttl("s"));
        //为数据添加超时时间
        jedis.setex("s", 20, "为数据添加超时111");
        System.out.println("获取超时时间:"+jedis.ttl("s"));
    }

    /**
     *  需求: 如果数据存在才修改,并且为数据添加超时时间,满足原子性要求
     *  SetParams:
     *          XX: 数据存在时赋值.
     *          NX: 数据不存在时赋值
     *          EX: 添加超时时间单位秒
     *          PX: 添加超时时间单位毫秒
     */
    @Test
    public void test03(){
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        jedis.flushAll();
        SetParams setParams = new SetParams();
        setParams.xx().ex(20);
        jedis.set("a", "测试方法",setParams);
        System.out.println(jedis.get("a"));
    }
}
1.3关于List集合说明
1.3.1关于队列应用场景

秒杀场景: 马上过年了, 店铺周年店庆 1部苹果12proMax 12000 1元秒杀? 提前预付活动费 10块… 如果秒杀不成功 则7日内退还?
在这里插入图片描述

1.3.2入门案例测试
@Test
    public void testList(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
        jedis.lpush("list", "1","2","3");
        System.out.println(jedis.rpop("list")); //队列
    }
1.4关于事物控制
 //弱事务控制
    @Test
    public void testTx(){
        Jedis jedis = new Jedis("192.168.126.129",6379);
        Transaction transaction = jedis.multi();  //开启事务
        try {
            transaction.set("k", "k");
            transaction.set("c", "c");
            transaction.exec();
        }catch (Exception e){
            e.printStackTrace();
            transaction.discard();
        }
    }

2.SpringBoot整合Redis

2.1编辑pro配置文件

说明:由于redis是公共的第三方,所以将配置放到jt-common中即可
在jt-common的resources目录下创建redis.properties配置文件
在这里插入图片描述

2.2编辑配置类

说明: 需要在jt-common中添加redis的配置类

package com.jt.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.Jedis;

@Configuration  //表示一个配置类  一般会与@Bean的注解联用
@PropertySource("classpath:/redis.properties") //导入配置文件
public class RedisConfig {

    @Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private Integer port;

    @Bean   //将方法的返回值结果,交给spring容器进行管理.
    public Jedis jedis(){
        return new Jedis(host, port);
    }
}
2.3测试redis案例

说明:springboot整合redis测试写在test中。

package com.jt.test;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;

@SpringBootTest //目的:动态获取spring容器中的数据
public class TestRedis {
    @Autowired
    private Jedis jedis;
    @Test
    public void testSpringJedis(){
        jedis.set("jedis", "spring对象测试");
        System.out.println(jedis.get("jedis"));
    }

3.JSON转化工具API

3.1入门案例测试
package com.jt.test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.pojo.ItemDesc;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TestObjectMapper {

    @Test
    public void test01() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        //将对象转化为JSON  调用的是对象的get方法获取属性/属性的值
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(1000L).setItemDesc("对象与json转化")
                .setCreated(new Date()).setUpdated(new Date());
        String json = objectMapper.writeValueAsString(itemDesc);
        System.out.println(json);

        //将JSON串转化为对象 调用的是对象的set方法为对象属性赋值
        ItemDesc itemDesc2 = objectMapper.readValue(json, ItemDesc.class);
        System.out.println(itemDesc2.getItemDesc());
    }

    @Test
    public void test02() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        //将对象转化为JSON  调用的是对象的get方法获取属性/属性的值
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(1000L).setItemDesc("对象与json转化").setCreated(new Date()).setUpdated(new Date());
        ItemDesc itemDesc2 = new ItemDesc();
        itemDesc2.setItemId(2000L).setItemDesc("对象与json转化2").setCreated(new Date()).setUpdated(new Date());

        List<ItemDesc> list2 = new ArrayList<>();
        list2.add(itemDesc);
        list2.add(itemDesc2);

        String json = objectMapper.writeValueAsString(list2);
        System.out.println(json);

        //将JSON串转化为对象 调用的是对象的set方法为对象属性赋值
        List list3 = objectMapper.readValue(json,list2.getClass());
        System.out.println(list3);
    }
}
3.2封装工具API
package com.jt.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.pojo.Item;
import com.jt.pojo.ItemDesc;
import com.sun.corba.se.spi.ior.IORTemplate;
/**
 * 该工具类,主要的功能实现对象与JSON串的互相转化.
 * 1.对象转化为JSON
 * 2.JSON转化为对象
 */
public class ObjectMapperUtil {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    //1.对象转化为JSON
    public static String toJSON(Object object){
        try {
            return MAPPER.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    //2.JSON转化为对象 要求用户传递什么类型就返回什么对象??
    public static <T> T toObj(String json,Class<T> target){
        try {
            return MAPPER.readValue(json, target);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

4.利用缓存实现商品分类查询

4.1业务说明

说明: 商品分类信息每次展开封闭的节点,都需要查询数据库.这样的效率并不高. 可以使用redis缓存来提升效率.
流程:
①用户第一次查询先查询缓存
②缓存中没有数据(这就是第一次查询),查询数据库. 将数据库记录保存到缓存中即可.
③缓存中有记录. 直接通过缓存获取数据之后返回即可.
在这里插入图片描述

4.2编辑ItemCatController
/**
     * 业务: 实现商品分类的查询
     * URL地址: http://localhost:8091/itemCat/list?id=xxx
     * 请求参数: 传递节点的ID
     * 返回值:  List<EasyUITree>对象   页面JS-VO~~~~POJO--DB
     */
    @RequestMapping("/list")
    public List<EasyUITree> findItemCatList(Long id){
        //1.查询一级商品分类信息
        Long parentId = (id==null?0L:id);
        //return itemCatService.findItemCatList(parentId);
        //利用redis缓存查询数据
        return itemCatService.findItemCatCache(parentId);
    }
4.3编辑ItemCatService
/**
     * 原理说明:
     *      1.定义存取redis中的key  业务名称+标识符  ITEMCAT_PARENTID::0
     *      2.通过key获取redis中的记录
     *      3.空:    查询数据库 将返回值结果保存到缓存中即可
     *      4.非空    直接将缓存数据获取之后,返回给用户即可.
     * @param parentId
     * @return
     */
    @Override
    public List<EasyUITree> findItemCatCache(Long parentId) {
        long startTime = System.currentTimeMillis();
        String key = "ITEMCAT_PARENTID::" + parentId;
        List treeList = new ArrayList();
        if(jedis.exists(key)){
            //如果存在则直接返回
            String json = jedis.get(key);
            treeList = ObjectMapperUtil.toObj(json, treeList.getClass());
            System.out.println("查询Redis缓存!!!");
            long endTime = System.currentTimeMillis();
            System.out.println("耗时:"+(endTime - startTime)+"毫秒");
        }else{
            //如果不存在 则查询数据库.
            treeList = findItemCatList(parentId);
            //将数据保存到缓存中
            String json = ObjectMapperUtil.toJSON(treeList);
            jedis.set(key,json);
            System.out.println("查询数据库!!!");
            long endTime = System.currentTimeMillis();
            System.out.println("耗时:"+(endTime - startTime)+"毫秒");
        }
        return treeList;
    }
4.4速度差

在这里插入图片描述

5.利用AOP实现商品分类缓存

5.1为什么使用AOP实现商品分类缓存

问题1: 如果将业务代码直接写死,那么该代码不具有通用性.
问题2: 代码冗余 代码的耦合性高.
AOP: 面向切面编程.
AOP作用: 在不修改原有方法的条件下.对原有的方法进行扩展.

5.2AOP复习

公式: AOP = 切入点表达式 + 通知方法

5.2.1通知方法

①before 目标方法执行之前执行
②afterThrowing 目标方法执行之后 抛出异常时执行
③afterReturning 目标方法执行之后 返回结果时执行
④after 目标方法执行之后执行(finally)
⑤around 环绕通知功能最为强大 可以控制目标方法的执行 在目标方法执行前后都要执行

5.2.2切入点表达式

①bean(bean的Id) 按照bean匹配!! Spring容器管理的对象称之为bean 粗粒度

②within(包名.类名) 按照包路径匹配 其中可以使用通配符代替
within("com.jt.service. ") 位于com.jt.service中的包的所有的类都会匹配. 粗粒度

③execution(返回值类型 包名.类名.方法名(参数列表)) 匹配的是方法参数级别 细粒度
execution(* com.jt.service…(…)) 解释:返回值类型任意 在com.jt.service的包路径中的任意类的任意方法的任意参数…
execution(* com.jt.service.userService.add*(int,String))

④@annotation(包名.注解名称) 按照注解匹配.
注解: @Find
@annotation(com.jt.anno.Find)

5.2.3关于AOP案例
package com.jt.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import java.util.Arrays;
/*@Service
@Controller
@Repository*/
@Component  //组件 将类交给spring容器管理
@Aspect     //表示我是一个切面
public class RedisAOP {
    //公式 aop = 切入点表达式   +   通知方法
    //@Pointcut("bean(itemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.*)")
    //@Pointcut("execution(* com.jt.service.*.*(..))")   //.* 当前包的一级子目录
    @Pointcut("execution(* com.jt.service..*.*(..))")  //..* 当前包的所有的子目录
    public void pointCut(){

    }
    //如何获取目标对象的相关参数?
    //ProceedingJoinPoint is only supported for around advice
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){    //连接点
        Object target = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("目标对象:"+target);
        System.out.println("方法参数:"+Arrays.toString(args));
        System.out.println("类名称:"+className);
        System.out.println("方法名称:"+methodName);
    }
    //作业: 利用自定义注解@CacheFind 实现缓存查询!!!!
}
5.3AOP实现缓存业务
5.3.1业务需求

(1)自定义注解 @CacheFind(key=“xxx”,second=-1)。
(2) 使用自定义注解 标识业务方法 将方法的返回值保存到缓存中。
(3) 利用AOP 拦截注解 利用环绕通知方法实现业务。

5.3.2自定义注解@CacheFind
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 {
    String key();//定义业务key
    int seconds() default -1;//定义超时时间 -1表示不超时
}
5.3.3注解标识

说明:在业务层需要使用redis缓存的业务上加@CacheFind注解

 /**
     * 核心问题: 将POJO对象转化为VO对象
     * @param parentId
     * @return
     */
    @Override
    @CacheFind(key = "ITEMCAT_PARENTID")//标识业务名称,一般都是大写的
    public List<EasyUITree> findItemCatList(Long parentId) {
        //2.定义VO的返回值
        List<EasyUITree> treeList = new ArrayList<>();
5.3.4编辑AOP
package com.jt.aop;
import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Method;
import java.util.Arrays;
/*@Service
@Controller
@Repository*/
@Component  //组件 将类交给spring容器管理
@Aspect     //表示我是一个切面
public class RedisAOP {

    @Autowired
    private Jedis jedis;

    /*
    * 实现AOP业务调用
    * 1.拦截指定的注解
    * 2.利用环绕通知实现
    * 实现步骤:
    *       1.获取KEY  必须先获取注解 从注解中获取key?
    *       2.校验redis中是否有值
    *     *
    * 3.知识点补充:
    *   指定参数名称进行传值,运行期绑定参数类型完成注解的拦截
    *   joinPoint必须位于参数的第一位.
    */
    @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
        Object result = null;
        //key=业务名称::参数
        String key = cacheFind.key();
        String args = Arrays.toString(joinPoint.getArgs());
        key = key + "::" + args;

        //2.校验是否有值
        if(jedis.exists(key)){
            String json = jedis.get(key);
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Class returnType = methodSignature.getReturnType();

            result = ObjectMapperUtil.toObj(json,returnType);
            System.out.println("AOP查询redis缓存");
        }else{
            //redis中没有数据,所以需要查询数据库,将数据保存到缓存中
            try {
                result = joinPoint.proceed();
                String json = ObjectMapperUtil.toJSON(result);
                //是否设定超时时间
                if(cacheFind.seconds()>0){
                    jedis.setex(key, cacheFind.seconds(), json);
                }else{
                    jedis.set(key,json);
                }
                System.out.println("AOP查询数据库");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        return result;
    }

    /**
     *   //1.获取key 注解  方法对象  类 方法名称  参数
     *         Class targetClass = joinPoint.getTarget().getClass();
     *         //2.获取方法对象
     *         String methodName = joinPoint.getSignature().getName();
     *         Object[] args = joinPoint.getArgs();
     *         Class[] classArgs = new Class[args.length];
     *         for(int i=0;i<args.length;i++){
     *             classArgs[i] = args[i].getClass();
     *         }
     *         try {
     *             //反射实例化对象
     *             Method method = targetClass.getMethod(methodName,classArgs);
     *             CacheFind cacheFind = method.getAnnotation(CacheFind.class);
     *             String key = cacheFind.key();
     *             System.out.println(key);
     *         } catch (NoSuchMethodException e) {
     *             e.printStackTrace();
     *         }
     */

    //公式 aop = 切入点表达式   +   通知方法
    //@Pointcut("bean(itemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.*)")
    //@Pointcut("execution(* com.jt.service.*.*(..))")   //.* 当前包的一级子目录
   /* @Pointcut("execution(* com.jt.service..*.*(..))")  //..* 当前包的所有的子目录
    public void pointCut(){

    }*/

    //如何获取目标对象的相关参数?
    //ProceedingJoinPoint is only supported for around advice
   /* @Before("pointCut()")
    public void before(JoinPoint joinPoint){    //连接点
        Object target = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("目标对象:"+target);
        System.out.println("方法参数:"+Arrays.toString(args));
        System.out.println("类名称:"+className);
        System.out.println("方法名称:"+methodName);
    }*/
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值