day 11 Redis
此文档是根据上课流程编写,更多细节及图片请参见刘老师的专栏
江哥的专栏
一. Redis客户端命令
Hash类型
i. 说明
可以用散列类型保存"对象"和属性值,一般在工作中存储的都是基于一个业务对象的数据。
user{"name":"aa","age":5}
ii. Hash类型命令
hset user name aa hset user age 5 hget user age => "5" hgetall user => "name" "aa" "age" "5" hkeys user => "name" "age"
List类型
i. 说明
双端循环链表,可以从首尾两端访问数据 L===R
ii. List类型命令
队列:lpush list 1 2 3 4 5 rpop ==> 1 栈:lpush list 1 2 3 4 5 lpop ==> 5
Set类型
sadd set1 a sadd set1 b scard set1 sinter set1 set2 sinterstore set3 set1 set2 sismember set1 c smembers set1
事务命令
Redis中的事务是弱事务,如果在分布式系统中无法对事务进行控制。
multi exec discard
二. 使用程序操作redis
整合入门案例
i. 导入jar包
redis.clients jedis org.springframework.data spring-data-redis ii. 创建TestRedis测试类
public class TestRedis { @Test //连接服务:IP地址:端口 public void testSetGet() throws InterruptedException { Jedis jedis = new Jedis("192.168.126.129",6379); jedis.flushAll(); //先清空redis缓存 //1.存取redis jedis.set("好好学习", "天天向上"); String value = jedis.get("好好学习"); System.out.println(value); //2.判断key是否存在 if(jedis.exists("好好学习")){ jedis.set("好好学习", "天天敲代码"); }else { jedis.set("好好学习", "天天开心"); }; //3.为key添加超时时间 jedis.expire("好好学习", 10); Thread.sleep(2000); System.out.println("剩余存活时间:"+jedis.ttl("好好学习")); //4.撤销剩余时间 jedis.persist("好好学习"); System.out.println("撤销成功"); } }
高级API
i. 测试setnx()
@Test //如果数据不存在,则修改数据 public void testSetNx() throws InterruptedException { Jedis jedis = new Jedis("192.168.126.129",6379); jedis.set("key1", "aaa"); //如果key不存在则赋值 jedis.setnx("key1", "测试方法"); System.out.println(jedis.get("key1")); //"aaa" }
ii. 测试setex()
@Test //实现超时时间的设定与赋值操作原子性 public void testSetEx() throws InterruptedException { Jedis jedis = new Jedis("192.168.126.129",6379); jedis.set("key2", "bbb"); jedis.expire("key2", 3); //数据超时之后一定会被删除吗? 错! //考点:为数据设定超时时间时,切记满足原子性操作,否则会出现key永远不能删除的现象 jedis.setex("key2", 3, "bbb"); }
iii. 测试SetParams
@Test //如果key存在,则为其赋值并添加超时时间 public void testSet() throws InterruptedException { Jedis jedis = new Jedis("192.168.126.129",6379); /*if(jedis.exists("key3")){ jedis.setex("key3", 10, "cc"); }*/ //NX - 只有数据不存在时才会赋值 XX - 只有数据存在时才会赋值 //EX - 设置超时时间:s PX - 设置超时时间:ms SetParams setParams = new SetParams(); setParams.xx().ex(10); //将所有的操作采用原子性的方式进行控制 jedis.set("key3", "ccc", setParams); }
iv. 测试Hash
@Test public void testHash() { Jedis jedis = new Jedis("192.168.126.129", 6379); jedis.hset("person", "id","100"); jedis.hset("person","name","Tomcat"); System.out.println(jedis.hgetAll("person")); }
v. 测试List
@Test public void testList(){ Jedis jedis = new Jedis("192.168.126.129", 6379); jedis.lpush("list", "1","2","3","4"); String value = jedis.rpop("list"); System.out.println(value); }
vi. 测试事务
@Test public void testMulti(){ Jedis jedis = new Jedis("192.168.126.129", 6379); //开启事务 Transaction transaction = jedis.multi(); try { transaction.set("a", "a"); transaction.set("b", "b"); transaction.set("c", "c"); //提交事务 transaction.exec(); }catch (Exception e){ //回滚事务 transaction.discard(); } }
三. SpringBoot整合redis
编写RedisConfig配置类
@Configuration //标识一个配置类 一般与@Bean注解连用 @PropertySource("classpath:/properties/redis.properties") public class RedisConfig { @Value("${redis.host}") private String host; @Value("${redis.port}") private Integer port; @Bean public Jedis jedis(){ return new Jedis(host,port); } }
修改TestRedis测试类
@SpringBootTest public class TestRedis { @Autowired private Jedis jedis; ... }
四. 基于业务实现缓存
ObjectMapperUtil实现
i. 业务需求
在业务中通常需要将业务对象
ii. 入门案例-编写TestObjectMapper测试类
public class TestObjectMapper { //objectMapper入门案例 @Test public void test01() throws JsonProcessingException { //1.将对象转化为JSON ItemDesc itemDesc = new ItemDesc(); itemDesc.setItemId(100L).setItemDesc("测试数据") .setCreated(new Date()).setUpdated(itemDesc.getCreated()); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(itemDesc); System.out.println(json); //2.将JSON转化为对象 ItemDesc desc = objectMapper.readValue(json, ItemDesc.class); System.out.println(desc); } }
//objectMapper入门案例 @Test public void test02() throws JsonProcessingException { List
list = new ArrayList<>(); //1.将集合转化为JSON ItemDesc itemDesc = new ItemDesc(); itemDesc.setItemId(100L).setItemDesc("测试数据") .setCreated(new Date()).setUpdated(itemDesc.getCreated()); ItemDesc itemDesc2 = new ItemDesc(); itemDesc2.setItemId(50L).setItemDesc("测试数据2") .setCreated(new Date()).setUpdated(itemDesc.getCreated()); list.add(itemDesc); list.add(itemDesc2); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(list); System.out.println(json); //2.将JSON转化为集合 List desc = objectMapper.readValue(json, list.getClass()); System.out.println(desc); } iii. 编辑ObjectMapperUtil工具API
public class ObjectMapperUtil { private static final ObjectMapper MAPPER = new ObjectMapper(); private static String toJSON(Object target){ try { return MAPPER.writeValueAsString(target); } catch (JsonProcessingException e) { e.printStackTrace(); //将检查异常转化为运行时异常 throw new RuntimeException(e); } } private static
T toObj(String json,Class clazz){ try { return MAPPER.readValue(json, clazz); } catch (JsonProcessingException e) { e.printStackTrace(); throw new RuntimeException(e); } } } 实现商品种类的缓存
i. 编辑ItemCatController
/* 业务需求:实现商品分类树形结构的展示 url: http://localhost:8091/item/cat/list 参数: id = 父级节点的id 返回值: List
*/ @RequestMapping("/list") public List showTree(Long id){ //暂时只查询一级菜单信息 long pid = (id==null)?0L:id; //数据库记录 ItemCat对象,页面中要的数据 EasyUITree //需要将pojo对象转换为vo对象 get/set... //List list = itemCatService.findAllCat(pid); //return list; return itemCatService.findItemCatCache(pid); } ii. 编辑ItemCatService/Impl
/* 实现步骤: 1.先定义key ITEM_CAT_PARENT::0 2.先查询缓存 T - 通过获取缓存数据之后,将JSON转化为对象,之后返回 F - 应该查询数据库,之后将数据保存到redis中,默认30天超时 */ @Autowired(required = false) private Jedis jedis; @Override public List
findItemCatCache(long pid) { List treeList = new ArrayList<>(); //1.定义key String key = "ITEM_CAT_PARENT::"+pid; //2. if(jedis.exists(key)){ String json = jedis.get(key); System.out.println("==查询缓存=="); treeList = ObjectMapperUtil.toObj(json,treeList.getClass()); }else { //不存在 treeList = findAllCat(pid); String json = ObjectMapperUtil.toJSON(treeList); jedis.setex(key,7*24*60*60, json); System.out.println("==查数据库=="); } return treeList; } AOP实现Redis缓存
i. AOP复习
1) 利用AOP可以实现对方法(功能)的扩展,实现代码的解耦;
2) 切面的组成要素:
切面 = 切入点表达式 + 通知方法
3) 切入点表达式
bean(bean的id) 拦截bean的所有方法,是粗粒度的 --> 具体的某个类 within(包名.类名) 扫描某个包下的某些类中的所有方法 com.jt.* 粗粒度的 execution(返回值类型 包名.类名.方法名(参数列表)) 细粒度的 !!! @annotation(包名.注解名) 细粒度的
4) 通知方法
before 目标方法执行前 afterReturning 目标方法返回后 afterThrowing 目标方法抛出异常后 after 不管什么情况,最后都要执行 around 目标方法执行前后都要执行
前四种通知类型一般用于记录程序运行的状态。如果要对程序运行的轨迹产生影响,首选around。
ii. AOP入门案例 - 编写CacheAop
@Aspect @Component public class CacheAop { //切面 = 切入点表达式+通知方法 //表达式1: ItemCatServiceImpl类 //@Pointcut("within(com.jt.service.*)") //.* 一级包下的类 ..* 所有子孙后代包中的类 //@Pointcut("execution(* com.jt.service..*.*(**))") @Pointcut("bean(itemCatServiceImpl)") //l默认值类名首字母小写 public void pointcut(){} @Before("pointcut()") public void before(JoinPoint joinPoint){ //jointPoint代表连接点的对象-程序执行的方法适用于前四大类型 //1.获取目标对象 Object target = joinPoint.getTarget(); System.out.println(target); //2.获取目标对象的路径 包名.类名.方法名 String className = joinPoint.getSignature().getDeclaringTypeName(); String methodName = joinPoint.getSignature().getName(); System.out.println("目标方法的路径: "+className+methodName); //3.获取参数类型 System.out.println(Arrays.toString(joinPoint.getArgs())); } @Around("pointcut()") public Object around(ProceedingJoinPoint joinPoint){ System.out.println("==环绕通知=="); Object data = null; try { data = joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } return data; } }