1.Redis入门案例
Redis命令: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 入门案例
package com.jt.redis;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;
import java.time.chrono.JapaneseDate;
public class TestRedis {
/**
* 1.redis配置文件3处修改
* 2.Linux系统防火墙
* 3.检查redis启动方式 redis-server redis.conf
* */
@Test
public void testSet() throws InterruptedException {
String host = "192.168.126.129"; //本地ip地址
int port = 6379; //redis启动后的端口号
Jedis jedis = new Jedis(host,port);
//命令怎么敲,代码就怎么写
jedis.set("2011", "redis入门案例"); //set添加
System.out.println(jedis.get("2011"));//get获取数据
jedis.flushAll();//清空全部数据库
//判断数据是否存在:
//测试是否存在
if(jedis.exists("2011")){//判断数据是否存在
jedis.del("2011");//删除redis中的数据
}else{
jedis.set("num", "100");//添加数据
//自增1
jedis.incr("num");//incr 自动加1
jedis.expire("num", 20);//指定num的生效时间 单位秒
Thread.sleep(2000);//睡眠2秒
//ttl检查数据的剩余存活时间
System.out.println(jedis.ttl("num"));//检查超时时间
//取消超时时间
jedis.persist("num");
}
}
//有时我们可能会为数据添加超时时间
//原子性问题: 有时业务要求要么同时完成,要么同时失败
@Test
public void testSetEx(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
//jedis.set("a","aa");//存值
//假设服务器异常,那么指定的生效时间则无法执行
//jedis.expire("a",60);//指定a的生效时间 60秒
jedis.setex("a", 20, "100");//setex把赋值操作和生效操作合二为一
System.out.println(jedis.get("a"));
}
/**
* 需求1:如果该数据不存在时,才会赋值.
* 需求2: 如果数据存在时,进行修改, 并且为他设定超时时间 满足原子性要求.
*
*/
@Test
public void testSetNX(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.set("a", "111");//存值
jedis.setnx("a", "123");//指定的数据不存在时,为数据设置指定的值。
System.out.println(jedis.get("a"));//获取a的值,并输出
// 需求2:
// if (jedis.expire("a"){//数据存在
// jedis.setex("a", 123, "60");//存值
// }
SetParams params = new SetParams();//创建对象
params.xx().ex(60);//nx数据不存在 赋值,xx数据存在 赋值,ex 秒 px毫秒
//判断数据是否存在,如果不存在就赋值
jedis.set("b", "bb", params);//b存在,就赋值
}
// hash类型
// 默认hash是无序的
@Test
public void testHash(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
//hset 为对象添加数据
jedis.hset("user", "id", "100");
jedis.hset("user", "name", "qingfeng");
jedis.hset("user", "age", "18");
//hkeys 获取hash中的所有字段
System.out.println(jedis.hkeys("user"));
//hget获取对象的属性值
System.out.println(jedis.hget("user", "name"));
//hmget 获取hash里面指定字段的值
System.out.println(jedis.hmget("user", "id"));
//hgetAll 获取hash全部元素和值
System.out.println(jedis.hgetAll("user"));
//hstrlen 获取hash中指定key的值的长度
System.out.println(jedis.hstrlen("user","name"));
}
//队列:先进先出 方向相反
//栈: 先进后出 方向相同 注意可变参数类型
@Test
public void testList(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
//L 4,3, 2,1 R
//lpush从队列的左边入队一个或多个元素
//jedis.lpush("list", "1,2,3,4"); 注意可变参数类型
jedis.lpush("list", "1","2","3","4");
//rpop 从队列的右端出队一个元素
System.out.println(jedis.rpop("list"));
}
@Test
public void testTx(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
//开启事务
//multi 标记一个事务开始
Transaction transaction = jedis.multi();
try {
transaction.set("a", "a");
transaction.set("b", "b");
transaction.set("b", "b");
//提交事务
transaction.exec();//exec 执行所有multi之后发的命令
}catch (Exception e){
transaction.discard(); //discard丢弃所有multi之后发的命令,回滚
}
}
}
2.SpringBoot整合Redis
2.1 编辑pro配置文件
2.2 编辑配置类
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 //标识为一个配置类,一般整合第三方
@PropertySource("classpath:/properties/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);
}
}
使用方法:
@Autowired
private Jedis jedis;
@Test
public void testSpringBootRedis(){
jedis.set("abc", "abc");
System.out.println(jedis.get("abc"));
}
3 JSON格式转化
3.1 业务说明
由于程序需要与JSON串进行数据的交互,所以通过API进行转化
3.2 代码如下
加深理解:
先在测试类中创建TestJSON方便理解
package com.jt.json;
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.Date;
public class TestJOSN {
/**
* json格式:
* 1.object格式 {key:value,key2:value2}
* 2.数组格式 [value,value2,value3]
* 3.嵌套格式 value可以进行嵌套.
*
* 任务: 实现对象与JSON串之间的转化.
* 面试题: 常量是否有线程安全性问题?
* (没有,多个线程共享资源,加了final不能被修改)
*/
//定义常量,final不能被修改,不加final可能会有安全性问题
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
public void ObjectToJSON() throws JsonProcessingException {
//创建对象
ItemDesc itemDesc = new ItemDesc();
//给对象赋值
itemDesc.setItemId(100L)
.setItemDesc("JSON测试")
.setCreated(new Date())
.setUpdated(itemDesc.getCreated());
//将对象转换为JSON,调用的对象get方法,形成属性/属性值
String json = MAPPER.writeValueAsString(itemDesc);
System.out.println(json);
//将JSON串,转化为对象 反射的机制 反射会实例化对象
//怎么给对象赋值?调用对象的set方法为属性赋值
ItemDesc itemDesc2 = MAPPER.readValue(json, ItemDesc.class);
System.out.println(itemDesc2.toString()+""+itemDesc2.getCreated());
}
}
运行测试:
加深结构理解:
package com.jt.json;
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 TestJOSN {
/**
* json格式:
* 1.object格式 {key:value,key2:value2}
* 2.数组格式 [value,value2,value3]
* 3.嵌套格式 value可以进行嵌套.
*
* 任务: 实现对象与JSON串之间的转化.
* 面试题: 常量是否有线程安全性问题?
* (没有,多个线程共享资源,加了final不能被修改)
*/
//定义常量,final不能被修改,不加final可能会有安全性问题
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
public void ObjectToJSON() throws JsonProcessingException {
//创建对象
ItemDesc itemDesc = new ItemDesc();
//给对象赋值
itemDesc.setItemId(100L)
.setItemDesc("JSON测试")
.setCreated(new Date())
.setUpdated(itemDesc.getCreated());
//将对象转换为JSON,调用的对象get方法,形成属性/属性值
String json = MAPPER.writeValueAsString(itemDesc);
System.out.println(json);
//将JSON串,转化为对象 反射的机制 反射会实例化对象
//怎么给对象赋值?调用对象的set方法为属性赋值
ItemDesc itemDesc2 = MAPPER.readValue(json, ItemDesc.class);
System.out.println(itemDesc2.toString()+""+itemDesc2.getCreated());
}
//2.加深印象:
//新建测试数据,封装到List集合
public List<ItemDesc> getList(){
List<ItemDesc> list = new ArrayList<>();
ItemDesc itemDesc = new ItemDesc();
//第一个对象
itemDesc.setItemId(100L)
.setItemDesc("bbb")
.setCreated(new Date())
.setUpdated(new Date());
//第二个对象
ItemDesc itemDesc2 = new ItemDesc();
itemDesc2.setItemId(100L)
.setItemDesc("bbb")
.setCreated(new Date())
.setUpdated(new Date());
list.add(itemDesc);
list.add(itemDesc2);
return list;
}
@Test
public void ListToJSON() throws JsonProcessingException {
List<ItemDesc> list = getList();
//转换为JSON串
String json = MAPPER.writeValueAsString(list);
//输出JSON串
System.out.println(json);
//将JSON还原回对象
List<ItemDesc> list2 = MAPPER.readValue(json, List.class);//传的是集合,转换回来还是集合
System.out.println(list2);
}
}
测试运行结果:
3.3 JSON转化工具API
说明: 在jt-common中添加工具API
工具类不需要交给容器管理
package com.jt.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ObjectMapperUtil {
//创建一个Object对象,通过这个对象进行数据的转换
private static final ObjectMapper MAPPER = new ObjectMapper();
//1.对象转化为JSON
public static String toJSON(Object obj){
try {
//没有问题就return把数据返回
return MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
//将检查异常,转化成运行时异常
e.printStackTrace();
//抛出异常
throw new RuntimeException(e);
}
}
//2.JSON转化为对象
//传递什么样的类型,返回什么样的对象???
//<T> 类型的定义
public static <T> T toObject(String json, Class<T> target){// Class<T> target 泛型
try {
//JSON转化为对象
return MAPPER.readValue(json, target);
} catch (JsonProcessingException e) {
//将检查异常,转化成运行时异常
e.printStackTrace();
//抛出异常
throw new RuntimeException(e);
}
}
}
4 商品分离缓存实现
4.1 编辑工具ItemCatServiceImpl
修改 ItemCatController
@RequestMapping("/item/cat/list")
public List<EasyUITree> findItemCatList(Long id){
//查询商品分类信息 1级菜单
//如果用户没有点击按钮 将不会传递Id值,应该设定默认值
long parentId = (id==null?0:id); //0 一级菜单 判断是否点击,点击了就传值
// return itemCatService.findItemCatList(parentId);
return itemCatService.findItemCatCache(parentId);
}
/**
*
* 1.将商品分类信息添加到缓存中
* 2.数据结构: k-v结构 ITEM_CAT_PARENT::0 - JSON
* 3.业务流程:
* 1.先查缓存 true/false
* 2.false 用户第一次查询数据库, 将结果保存到redis缓存中
* 3.true 查询缓存
* AOP机制
*/
@Autowired(required = false)
private Jedis jedis;//该参数不是最终结构,后期需要优化
@Override
public List<EasyUITree> findItemCatCache(long parentId) {
//起始时间
long startTime = System.currentTimeMillis();
//准备一个空集合
List<EasyUITree> treeList = new ArrayList<>();
//1.定义key
String key = "ITEM_CAT_PARENT::" + parentId;
//2.判断数据是否存在
if(jedis.exists(key)){
//根据key查询json
String json = jedis.get(key);
//json转化为对象
treeList = ObjectMapperUtil.toObject(json, treeList.getClass());
//结束查询时间
long endTime = System.currentTimeMillis();
//输出查询结果
System.out.println("redis查询! 耗时:"+(endTime-startTime));
}else{
//数据不存在,查询数据库
treeList = findItemCatList(parentId);
//将数据库记录,转化为JSON
String json = ObjectMapperUtil.toJSON(treeList);
// 将数据保存到redis中
jedis.set(key, json);
//结束查询时间
long endTime = System.currentTimeMillis();
//输出查询结果
System.out.println("查询数据库!! 耗时:"+(endTime-startTime));
}
return treeList;
}
4.2 Redis效果展现
5 Spring中AOP复习
5.1 什么是AOP
名称解释: 面向切面编程
作用: 在不修改源码的条件下,对方法 功能进行了额外了扩展.
意义: 降低了代码的耦合性.
公式: AOP = 切入点表达式 + 通知方法
5.2 切入点表达式
1.bean(“包名.类名”) 具体某个类 按类匹配 粗粒度
2.within(“包名.类名”) 可以使用通配符 几个类一起匹配 粗粒度
3.execution(返回值类型 包名.类名.方法名(参数列表)) 细粒度
4.注解方式 @annotation(注解的类型) 细粒度
5.3 通知方法
1.before 目标方法执行之前执行.
2.afterReturning 目标方法执行之后执行
3.afterThrowing 目标方法抛出异常时执行
4.after 不管上述操作如何运行,最终都要执行.
上述的4大通知,都不能控制目标方法是否执行,一般只会做程序的监控.
5.around 在目标方法执行前后 都要执行. 可以控制目标方法的执行轨迹.
5.4 AOP入门案例
5.5 代码测试理解
把控制类 ItemCatController 刚刚修改的内容还原
@RequestMapping("/item/cat/list")
public List<EasyUITree> findItemCatList(Long id){
//查询商品分类信息 1级菜单
//如果用户没有点击按钮 将不会传递Id值,应该设定默认值
long parentId = (id==null?0:id); //0 一级菜单 判断是否点击,点击了就传值
return itemCatService.findItemCatList(parentId);
//return itemCatService.findItemCatCache(parentId);
}
在jt_common中创建RedisAOP 类
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;
@Component //将我们的对象交给Spring容器管理
@Aspect //表示AOP切面
public class RedisAop {
/**
* 1.定义切入点表达式
* bean: 被spring容器管理的对象称之为 bean
* 1.1 bean(bean的ID) 按类匹配 1个
* bean(itemCatServiceImpl)
*
* 1.2 within(包名.类名) 按类匹配 一堆
* within(com.jt.service.*)
*
* 1.3 execution(返回值类型 包名.类名.方法名(参数列表))
* execution(* com.jt.service..*.*(..)) 万能的表达式
* service. 一级
* service.. 多级
* 解释: 返回值为任意类型 com.jt.service包所有的子孙包的类
* 类中的任意方法,任意参数
* execution(Integer com.jt.service..*.add*(int)) //只能是包装类型
* execution(int com.jt.service..*.add*(int)) 只能是基本类型
*/
//@Pointcut("execution(* com.jt.service..*.*(..))")
@Pointcut("bean(itemCatServiceImpl)")
public void pointCut(){
}
//Aop如何与目标方法进行关联? JoinPoint(连接点)
//如何理解什么是连接点? 被切入点拦截的方法(连接点=方法)
//ProceedingJoinPoint is only supported for around advice
//只有环绕通知可以控制目标方法
@Before("pointCut()") //乌龟的屁股 规定
public void before(JoinPoint joinPoint){ //前置通知
//获取目标方法的名称
String methodName = joinPoint.getSignature().getName();
//获取目标类的名称
String className = joinPoint.getSignature().getDeclaringTypeName();
//获取目标的参数
Object[] args = joinPoint.getArgs();
//获取目标的对象
Object target = joinPoint.getTarget();
//输出
System.out.println(methodName);
System.out.println(className);
System.out.println(args);
System.out.println(target);
System.out.println("我是一个前置通知");
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕开始");
Object result = joinPoint.proceed(); //执行下一个通知,目标方法
System.out.println("环绕结束");
return result;
}
}