AOP删除redis缓存

前言

当redis作为缓存时候,mysql数据库数据发生改变,那么缓存数据就不是最新的,所以需要清楚redis中缓存数据

但是每次增删改都要再方法中清楚缓存,显得比较麻烦

利用aop的特性,在执行insert,update,delete方法时候,我们使用增强方法清楚缓存,比如使用前置通知

步骤

1.新建一个springboot工程

说明:

  • 测试期间,所以省略了一些操作

省略了dao层

省略了连接mysql数据库的过程

使用java工具库制造一些假数据

  • java工具库生成假数据

参考:

http://119.45.152.156:8090/archives/java%E5%88%9B%E9%80%A0%E5%81%87%E6%95%B0%E6%8D%AE%E7%9A%84%E5%B7%A5%E5%85%B7%E5%BA%93md

  • crud方法的特点

insert,update,delete方法的返回值有三种

void,int,boolean

select方法的返回值有两种

实体类,ArrayList集合

所以我们对void和int返回值的方法进行切面控制就可以

2.引入测试需要的依赖

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.shaoming</groupId>
    <artifactId>springboot-test-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-test-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <!-- aop相关依赖 (@Aspect这个注解需要这个依赖) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- 引入hutool工具类 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.6</version>
        </dependency>
        <!-- java制造假数据的依赖 -->
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>1.0.2</version>
        </dependency>
        <!--redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3.模拟crud的业务方法

3.1定义实体类

UserInfo

package com.shaoming.entity;

import lombok.Data;

/**
 * @Auther: shaoming
 * @Date: 2021/1/2 14:22
 * @Description:
 */
@Data
public class UserInfo {
    private String id;
    /**
     * 真实姓名
     */
    private String realName;
    /**
     * 手机
     */
    private String cellPhone;
    /**
     * 大学
     */
    private String universityName;
    /**
     * 城市
     */
    private String city;
    /**
     * 地址
     */
    private String street;
}

3.2定义service接口

package com.shaoming.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.shaoming.entity.UserInfo;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Auther: shaoming
 * @Date: 2021/1/2 14:22
 * @Description:
 */
public interface UserInfoService {
    public int insert();
    public int updateById();
    public void deleteById();
    public List<UserInfo> selectList() throws JsonProcessingException;
}

3.3 定义service实现类

package com.shaoming.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.javafaker.Faker;
import com.shaoming.JacksonUtil;
import com.shaoming.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * @Auther: shaoming
 * @Date: 2021/1/2 14:25
 * @Description:
 */
@Service
public class UserInfoServiceImpl implements UserInfoService {
     public static final String USER_INFO_DATA="userInfos";
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public int insert() {
        System.out.println("模拟添加");
        return 1;
    }

    @Override
    public int updateById() {
        System.out.println("模拟更新");
        return 1;
    }

    @Override
    public void deleteById() {
        System.out.println("模拟删除");
    }

    @Override
    public List<UserInfo> selectList() throws JsonProcessingException {
        Faker fakerWithCN = new Faker(Locale.CHINA);
        ArrayList<UserInfo> userInfos = new ArrayList<>();

        String userInfos1 = stringRedisTemplate.opsForValue().get(USER_INFO_DATA);
        if(userInfos1==null){
            for (int i = 0; i < 10; i++) {
                UserInfo userInfo = new UserInfo();
                userInfo.setId(String.valueOf(i));
                userInfo.setRealName(fakerWithCN.name().fullName());
                userInfo.setCellPhone(fakerWithCN.phoneNumber().cellPhone());
                userInfo.setCity(fakerWithCN.address().city());
                userInfo.setStreet(fakerWithCN.address().streetAddress());
                userInfo.setUniversityName(fakerWithCN.university().name());
//            System.out.println("userInfo = " + userInfo);
                userInfos.add(userInfo);
            }
               stringRedisTemplate.opsForValue().set("userInfos",new ObjectMapper().writeValueAsString(userInfos));
            System.out.println("模拟从mysql数据库中查出数据");
            return userInfos;
        }else {
            System.out.println("模拟从redis数据库中查出数据(redis作为缓存)");
            return JacksonUtil.jsonToObj(userInfos1, ArrayList.class);
        }
    }
}

3.4定义返回的vo类

package com.shaoming.entity;


import java.util.HashMap;
import java.util.Map;

/**
 * 返回数据
 *
 * @author Mark sunlightcs@gmail.com
 */
public class R extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;
//    private Integer code;
//    private String msg;
//    private Object data;
    public R() {
        put("code", 0);
    }

    public static R error() {
        return error(500, "未知异常,请联系管理员");
    }

    public static R error(String msg) {
        return error(500, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }

    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}

3.5定义json数据处理的工具类

使用的是Jackson

package com.shaoming;

import java.io.IOException;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;


public class JacksonUtil {


    private static ObjectMapper mapper = new ObjectMapper();


    /**
     * 对象转Json格式字符串
     * @param obj 对象
     * @return Json格式字符串
     */
    public static <T> String obj2String(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj : mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 对象转Json格式字符串(格式化的Json字符串)
     * @param obj 对象
     * @return 美化的Json格式字符串
     */
    public static <T> String obj2StringPretty(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj : mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
        	e.printStackTrace();
            return null;
        }
    }

    /**
     * 字符串转换为自定义对象
     * @param str 要转换的字符串
     * @param clazz 自定义对象的class对象
     * @return 自定义对象
     */
    @SuppressWarnings("unchecked")
	public static <T> T jsonToObj(String str, Class<T> clazz){
        if(StrUtil.isEmpty(str) || clazz == null){
            return null;
        }
        try {
            return clazz.equals(String.class) ? (T) str : mapper.readValue(str, clazz);
        } catch (Exception e) {
        	e.printStackTrace();
            return null;
        }
    }


    /**
     * 集合对象与Json字符串之间的转换
     * @param str 要转换的字符串
     * @param typeReference 集合类型如List<Object>
     * @param <T> 
     * @return
     */
    @SuppressWarnings("unchecked")
	public static <T> T jsonToObj(String str, TypeReference<T> typeReference) {
        if (StrUtil.isEmpty(str) || typeReference == null) {
            return null;
        }
        try {
            return (T) (typeReference.getType().equals(String.class) ? str : mapper.readValue(str, typeReference));
        } catch (IOException e) {
        	e.printStackTrace();
            return null;
        }
    }

    /**
     * 集合对象与Json字符串之间的转换
     * @param str 要转换的字符串
     * @param collectionClazz 集合类型
     * @param elementClazzes 自定义对象的class对象
     * @param <T>
     * @return
     */
    public static <T> T string2Obj(String str, Class<?> collectionClazz, Class<?>... elementClazzes) {
        JavaType javaType = mapper.getTypeFactory().constructParametricType(collectionClazz, elementClazzes);
        try {
            return mapper.readValue(str, javaType);
        } catch (IOException e) {
        	e.printStackTrace();
            return null;
        }
    }
}

3.6定义controller测试类

package com.shaoming.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.shaoming.entity.R;
import com.shaoming.entity.UserInfo;
import com.shaoming.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * @Auther: shaoming
 * @Date: 2021/1/2 14:45
 * @Description:
 */
@RestController
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;
     @GetMapping("/insert")//get请求
         public R insert() {
         int insert = userInfoService.insert();
         return R.ok().put("message","添加成功");
         }
     @GetMapping("/delete")//get请求
         public R delete() {
         userInfoService.deleteById();
         return R.ok().put("message","删除成功");
         }

     @GetMapping("/update")//get请求
         public R updateById() {
         userInfoService.updateById();
         return R.ok().put("message","更新成功");
         }
     @GetMapping("/select")//get请求
         public  R select() throws JsonProcessingException {
         List<UserInfo> userInfos = userInfoService.selectList();
         return R.ok().put("data",userInfos);
         }
}

4.aop清除缓存

定义切面类

package com.shaoming.aop;

import com.shaoming.service.UserInfoServiceImpl;
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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

/**
 * @Auther: shaoming
 * @Date: 2021/1/2 14:55
 * @Description:
 * 例用切面删除缓存
 * 当执行增删改的时候我们需要清理缓存
 */
@Aspect//表名这是一个切面类
@Component//切面需要交给spring容器管理
public class ServiceDeleteCacheAspect {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Pointcut("execution(public void com.shaoming.service.*.* (..))")
    public void pointvoid(){

    }
    @Pointcut("execution(public int com.shaoming.service.*.* (..))")
    public void pointint(){

    }
    @Before("pointint()")
    public void doBefore(JoinPoint joinPoint){
        System.out.println("前置通知:===(返回值是int)===="+joinPoint);
                stringRedisTemplate.delete(UserInfoServiceImpl.USER_INFO_DATA);
    }
    @Before("pointvoid()")
    public void doVoidBefore(JoinPoint joinPoint){
        System.out.println("前置通知:===(返回值是void)===="+joinPoint);
                stringRedisTemplate.delete(UserInfoServiceImpl.USER_INFO_DATA);
    }
}

5.测试

4.1测试查询的数据库

localhost:8080/select

第一次请求

控制台打印

模拟从mysql数据库中查出数据

第二次请求

控制台打印

模拟从redis数据库中查出数据(redis作为缓存)

证明模拟redis作为缓存成功

4.2测试增删改时候清除缓存

下面请求的控制台打印如下:

localhost:8080/insert

前置通知:===(返回值是int)====execution(int com.shaoming.service.UserInfoServiceImpl.insert())
模拟添加

localhost:8080/update

前置通知:===(返回值是int)====execution(int com.shaoming.service.UserInfoServiceImpl.updateById())
模拟更新

localhost:8080/delete

前置通知:===(返回值是void)====execution(void com.shaoming.service.UserInfoServiceImpl.deleteById())
模拟删除

最后请求

localhost:8080/select

控制台打印

模拟从mysql数据库中查出数据