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数据库中查出数据